<!DOCTYPE html><html lang="[&quot;zh-CN&quot;,&quot;en&quot;,&quot;default&quot;]" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0,viewport-fit=cover"><title>SpringCloud 微服务入门：服务调用流程解析 | 爱吃薯片的熊猫の技术小站</title><meta name="author" content="爱吃薯片的熊猫"><meta name="copyright" content="爱吃薯片的熊猫"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="ffffff"><meta name="description" content="记录了在 CentOS 7 上安装 Docker 和 MySQL 时遇到的问题及解决方案，帮助你更顺利地搭建环境，避免常见的坑。">
<meta property="og:type" content="article">
<meta property="og:title" content="SpringCloud 微服务入门：服务调用流程解析">
<meta property="og:url" content="https://ywj-ch.github.io/2025/01/25/%E5%BE%AE%E6%9C%8D%E5%8A%A1%E5%85%A5%E9%97%A8%EF%BC%9A%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8%E6%B5%81%E7%A8%8B%E8%A7%A3%E6%9E%90/index.html">
<meta property="og:site_name" content="爱吃薯片的熊猫の技术小站">
<meta property="og:description" content="记录了在 CentOS 7 上安装 Docker 和 MySQL 时遇到的问题及解决方案，帮助你更顺利地搭建环境，避免常见的坑。">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://ywj-ch.github.io/img/SpringCloud%20%E5%BE%AE%E6%9C%8D%E5%8A%A1%E5%85%A5%E9%97%A8%EF%BC%9A%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8%E6%B5%81%E7%A8%8B%E8%A7%A3%E6%9E%90.png">
<meta property="article:published_time" content="2025-01-25T12:08:31.000Z">
<meta property="article:modified_time" content="2025-02-02T12:21:37.477Z">
<meta property="article:author" content="爱吃薯片的熊猫">
<meta property="article:tag" content="springcloud">
<meta property="article:tag" content="微服务">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://ywj-ch.github.io/img/SpringCloud%20%E5%BE%AE%E6%9C%8D%E5%8A%A1%E5%85%A5%E9%97%A8%EF%BC%9A%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8%E6%B5%81%E7%A8%8B%E8%A7%A3%E6%9E%90.png"><script type="application/ld+json">{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "SpringCloud 微服务入门：服务调用流程解析",
  "url": "https://ywj-ch.github.io/2025/01/25/%E5%BE%AE%E6%9C%8D%E5%8A%A1%E5%85%A5%E9%97%A8%EF%BC%9A%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8%E6%B5%81%E7%A8%8B%E8%A7%A3%E6%9E%90/",
  "image": "https://ywj-ch.github.io/img/SpringCloud%20%E5%BE%AE%E6%9C%8D%E5%8A%A1%E5%85%A5%E9%97%A8%EF%BC%9A%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8%E6%B5%81%E7%A8%8B%E8%A7%A3%E6%9E%90.png",
  "datePublished": "2025-01-25T12:08:31.000Z",
  "dateModified": "2025-02-02T12:21:37.477Z",
  "author": [
    {
      "@type": "Person",
      "name": "爱吃薯片的熊猫",
      "url": "https://ywj-ch.github.io/"
    }
  ]
}</script><link rel="shortcut icon" href="/img/favicon.png"><link rel="canonical" href="https://ywj-ch.github.io/2025/01/25/%E5%BE%AE%E6%9C%8D%E5%8A%A1%E5%85%A5%E9%97%A8%EF%BC%9A%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8%E6%B5%81%E7%A8%8B%E8%A7%A3%E6%9E%90/index.html"><link rel="preconnect" href="//cdnjs.cloudflare.com"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css"><script>
    (() => {
      
    const saveToLocal = {
      set: (key, value, ttl) => {
        if (!ttl) return
        const expiry = Date.now() + ttl * 86400000
        localStorage.setItem(key, JSON.stringify({ value, expiry }))
      },
      get: key => {
        const itemStr = localStorage.getItem(key)
        if (!itemStr) return undefined
        const { value, expiry } = JSON.parse(itemStr)
        if (Date.now() > expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return value
      }
    }

    window.btf = {
      saveToLocal,
      getScript: (url, attr = {}) => new Promise((resolve, reject) => {
        const script = document.createElement('script')
        script.src = url
        script.async = true
        Object.entries(attr).forEach(([key, val]) => script.setAttribute(key, val))
        script.onload = script.onreadystatechange = () => {
          if (!script.readyState || /loaded|complete/.test(script.readyState)) resolve()
        }
        script.onerror = reject
        document.head.appendChild(script)
      }),
      getCSS: (url, id) => new Promise((resolve, reject) => {
        const link = document.createElement('link')
        link.rel = 'stylesheet'
        link.href = url
        if (id) link.id = id
        link.onload = link.onreadystatechange = () => {
          if (!link.readyState || /loaded|complete/.test(link.readyState)) resolve()
        }
        link.onerror = reject
        document.head.appendChild(link)
      }),
      addGlobalFn: (key, fn, name = false, parent = window) => {
        if (!false && key.startsWith('pjax')) return
        const globalFn = parent.globalFn || {}
        globalFn[key] = globalFn[key] || {}
        globalFn[key][name || Object.keys(globalFn[key]).length] = fn
        parent.globalFn = globalFn
      }
    }
  
      
      const activateDarkMode = () => {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      const activateLightMode = () => {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', 'ffffff')
        }
      }

      btf.activateDarkMode = activateDarkMode
      btf.activateLightMode = activateLightMode

      const theme = saveToLocal.get('theme')
    
          const hour = new Date().getHours()
          const isNight = hour <= 6 || hour >= 18
          if (theme === undefined) isNight ? activateDarkMode() : activateLightMode()
          else theme === 'light' ? activateLightMode() : activateDarkMode()
        
      
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        document.documentElement.classList.toggle('hide-aside', asideStatus === 'hide')
      }
    
      
    const detectApple = () => {
      if (/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)) {
        document.documentElement.classList.add('apple')
      }
    }
    detectApple()
  
    })()
  </script><script>const GLOBAL_CONFIG = {
  root: '/',
  algolia: undefined,
  localSearch: {"path":"/search.xml","preload":false,"top_n_per_article":1,"unescape":false,"languages":{"hits_empty":"未找到符合您查询的内容：${query}","hits_stats":"共找到 ${hits} 篇文章"}},
  translate: undefined,
  highlight: {"plugin":"highlight.js","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":false,"highlightFullpage":false,"highlightMacStyle":false},
  copy: {
    success: '复制成功',
    error: '复制失败',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: false,
    post: false
  },
  runtime: '',
  dateSuffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: undefined,
  lightbox: 'null',
  Snackbar: undefined,
  infinitegrid: {
    js: 'https://cdnjs.cloudflare.com/ajax/libs/egjs-infinitegrid/4.12.0/infinitegrid.min.js',
    buttonText: '加载更多'
  },
  isPhotoFigcaption: false,
  islazyloadPlugin: false,
  isAnchor: false,
  percent: {
    toc: true,
    rightside: false,
  },
  autoDarkmode: false
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
  title: 'SpringCloud 微服务入门：服务调用流程解析',
  isHighlightShrink: false,
  isToc: true,
  pageType: 'post'
}</script><link rel="stylesheet" href="/css/custom.css" media="defer" onload="this.media='all'"><span id="fps"></span><svg aria-hidden="true" style="position:absolute; overflow:hidden; width:0; height:0"><symbol id="icon-sun" viewBox="0 0 1024 1024"><path d="M960 512l-128 128v192h-192l-128 128-128-128H192v-192l-128-128 128-128V192h192l128-128 128 128h192v192z" fill="#FFD878" p-id="8420"></path><path d="M736 512a224 224 0 1 0-448 0 224 224 0 1 0 448 0z" fill="#FFE4A9" p-id="8421"></path><path d="M512 109.248L626.752 224H800v173.248L914.752 512 800 626.752V800h-173.248L512 914.752 397.248 800H224v-173.248L109.248 512 224 397.248V224h173.248L512 109.248M512 64l-128 128H192v192l-128 128 128 128v192h192l128 128 128-128h192v-192l128-128-128-128V192h-192l-128-128z" fill="#4D5152" p-id="8422"></path><path d="M512 320c105.888 0 192 86.112 192 192s-86.112 192-192 192-192-86.112-192-192 86.112-192 192-192m0-32a224 224 0 1 0 0 448 224 224 0 0 0 0-448z" fill="#4D5152" p-id="8423"></path></symbol><symbol id="icon-moon" viewBox="0 0 1024 1024"><path d="M611.370667 167.082667a445.013333 445.013333 0 0 1-38.4 161.834666 477.824 477.824 0 0 1-244.736 244.394667 445.141333 445.141333 0 0 1-161.109334 38.058667 85.077333 85.077333 0 0 0-65.066666 135.722666A462.08 462.08 0 1 0 747.093333 102.058667a85.077333 85.077333 0 0 0-135.722666 65.024z" fill="#FFB531" p-id="11345"></path><path d="M329.728 274.133333l35.157333-35.157333a21.333333 21.333333 0 1 0-30.165333-30.165333l-35.157333 35.157333-35.114667-35.157333a21.333333 21.333333 0 0 0-30.165333 30.165333l35.114666 35.157333-35.114666 35.157334a21.333333 21.333333 0 1 0 30.165333 30.165333l35.114667-35.157333 35.157333 35.157333a21.333333 21.333333 0 1 0 30.165333-30.165333z" fill="#030835" p-id="11346"></path></symbol></svg><!-- hexo injector head_end start --><link rel="stylesheet" href="https://npm.elemecdn.com/hexo-butterfly-swiper/lib/swiper.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://npm.elemecdn.com/hexo-butterfly-swiper/lib/swiperstyle.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://npm.elemecdn.com/hexo-butterfly-wowjs/lib/animate.min.css" media="print" onload="this.media='screen'"><!-- hexo injector head_end end --><meta name="generator" content="Hexo 7.3.0"></head><body><div id="web_bg" style="background-image: url(/img/背景图3.jpg);"></div><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img text-center"><img src="/img/loading.gif" data-original="/img/butterfly-icon.png" onerror="this.onerror=null;this.src='/img/friend_404.gif'" alt="avatar"/></div><div class="site-data text-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">6</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">9</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">3</div></a></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 归档</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div><div class="menus_item"><span class="site-page group hide"><i class="fa-fw fa fa-list"></i><span> 列表</span><i class="fas fa-chevron-down"></i></span><ul class="menus_item_child"><li><a class="site-page child" href="/music/"><i class="fa-fw fas fa-music"></i><span> 音乐</span></a></li><li><a class="site-page child" href="/picture/"><i class="fa-fw fas fa-images"></i><span> 照片</span></a></li><li><a class="site-page child" href="/movies/"><i class="fa-fw fas fa-video"></i><span> 电影</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="/comments/"><i class="fa-fw fas fa-envelope-open"></i><span> 留言板</span></a></div><div class="menus_item"><a class="site-page" href="/link/"><i class="fa-fw fas fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/about/"><i class="fa-fw fas fa-heart"></i><span> 关于</span></a></div></div></div></div><div class="post" id="body-wrap"><header class="post-bg" id="page-header" style="background-image: url(/img/SpringCloud 微服务入门：服务调用流程解析.png);"><nav id="nav"><span id="blog-info"><a class="nav-site-title" href="/"><span class="site-name">爱吃薯片的熊猫の技术小站</span></a><a class="nav-page-title" href="/"><span class="site-name">SpringCloud 微服务入门：服务调用流程解析</span></a></span><div id="menus"><div id="search-button"><span class="site-page social-icon search"><i class="fas fa-search fa-fw"></i></span></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 归档</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div><div class="menus_item"><span class="site-page group hide"><i class="fa-fw fa fa-list"></i><span> 列表</span><i class="fas fa-chevron-down"></i></span><ul class="menus_item_child"><li><a class="site-page child" href="/music/"><i class="fa-fw fas fa-music"></i><span> 音乐</span></a></li><li><a class="site-page child" href="/picture/"><i class="fa-fw fas fa-images"></i><span> 照片</span></a></li><li><a class="site-page child" href="/movies/"><i class="fa-fw fas fa-video"></i><span> 电影</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="/comments/"><i class="fa-fw fas fa-envelope-open"></i><span> 留言板</span></a></div><div class="menus_item"><a class="site-page" href="/link/"><i class="fa-fw fas fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/about/"><i class="fa-fw fas fa-heart"></i><span> 关于</span></a></div></div><div id="toggle-menu"><span class="site-page"><i class="fas fa-bars fa-fw"></i></span></div></div></nav><div id="post-info"><h1 class="post-title">SpringCloud 微服务入门：服务调用流程解析</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2025-01-25T12:08:31.000Z" title="发表于 2025-01-25 20:08:31">2025-01-25</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2025-02-02T12:21:37.477Z" title="更新于 2025-02-02 20:21:37">2025-02-02</time></span><span class="post-meta-categories"><span class="post-meta-separator">|</span><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/">学习笔记</a></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-wordcount"><i class="far fa-file-word fa-fw post-meta-icon"></i><span class="post-meta-label">总字数:</span><span class="word-count">8.4k</span><span class="post-meta-separator">|</span><i class="far fa-clock fa-fw post-meta-icon"></i><span class="post-meta-label">阅读时长:</span><span>28分钟</span></span><span class="post-meta-separator">|</span><span class="post-meta-pv-cv" id="" data-flag-title=""><i class="far fa-eye fa-fw post-meta-icon"></i><span class="post-meta-label">浏览量:</span><span id="busuanzi_value_page_pv"><i class="fa-solid fa-spinner fa-spin"></i></span></span></div></div></div></header><main class="layout" id="content-inner"><div id="post"><article class="container post-content" id="article-container"><p>目录</p>
<p><a href="#%E4%B8%80%E3%80%81%E5%BC%95%E8%A8%80">1、引言</a></p>
<p><a href="#%E4%BA%8C%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1%E5%9F%BA%E7%A1%80%E6%A6%82%E5%BF%B5">2、微服务基础概念</a></p>
<p><a href="#2.1%E3%80%81%E6%A1%86%E6%9E%B6%E6%80%BB%E8%A7%88">2.1、框架总览</a></p>
<p><a href="#2.2%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1%E4%B8%8E%E5%8D%95%E4%BD%93%E6%9E%B6%E6%9E%84%E7%9A%84%E5%AF%B9%E6%AF%94">2.2、微服务与单体架构的对比</a></p>
<p><a href="#%E4%B8%89%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%B9%E7%82%B9%EF%BC%9A%E6%9C%8D%E5%8A%A1%E4%B9%8B%E9%97%B4%E7%9A%84%E8%B0%83%E7%94%A8">3、微服务的最大特点：服务之间的调用</a></p>
<p><a href="#3.1%E3%80%81%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C">3.1、服务注册</a></p>
<p><a href="#3.1.1%E3%80%81%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83%E5%8E%9F%E7%90%86">3.1.1、注册中心原理</a></p>
<p><a href="#3.1.2%E3%80%81Nacos%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83">3.1.2、Nacos 注册中心</a></p>
<p><a href="#3.1.3%E3%80%81%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C">3.1.3、服务注册</a></p>
<p><a href="#3.2%E3%80%81%E6%9C%8D%E5%8A%A1%E9%97%B4%E9%80%9A%E4%BF%A1%E6%96%B9%E5%BC%8F">3.2、服务间通信方式</a></p>
<p><a href="#3.2.1%E3%80%81OpenFeign">3.2.1、OpenFeign</a></p>
<p><a href="#3.2.2%E3%80%81RabbitMQ">3.2.2、RabbitMQ</a></p>
<p><a href="#3.2.3%E3%80%81SpringAMQP">3.2.3、SpringAMQP</a></p>
<p><a href="#3.3%E3%80%81%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1">3.3、负载均衡</a></p>
<p><a href="#3.3.1%E3%80%81%E6%BA%90%E7%A0%81%E8%B7%9F%E8%B8%AA">3.3.1、源码跟踪</a></p>
<p><a href="#3.3.2%E3%80%81NacosRule">3.3.2、NacosRule</a></p>
<p><a href="#3.4%E3%80%81%C2%A0%E6%9C%8D%E5%8A%A1%E5%AE%B9%E9%94%99%E6%9C%BA%E5%88%B6">3.4、 服务容错机制</a></p>
<p><a href="#3.4.1%E3%80%81%E6%9C%8D%E5%8A%A1%E4%BF%9D%E6%8A%A4%E6%96%B9%E6%A1%88">3.4.1、服务保护方案</a></p>
<p><a href="#3.4.2%E3%80%81Sentinel">3.4.2、Sentinel</a></p>
<p><a href="#3.4.3%E3%80%81%E8%AF%B7%E6%B1%82%E9%99%90%E6%B5%81">3.4.3、请求限流</a></p>
<p><a href="#3.4.4%E3%80%81%E7%BA%BF%E7%A8%8B%E9%9A%94%E7%A6%BB">3.4.4、线程隔离</a></p>
<p><a href="#3.4.5%E3%80%81%E6%9C%8D%E5%8A%A1%E7%86%94%E6%96%AD">3.4.5、服务熔断</a></p>
<p><a href="#3.5%E3%80%81%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1">3.5、分布式事务</a></p>
<p><a href="#3.5.1%E3%80%81Seata">3.5.1、Seata</a></p>
<p><a href="#3.5.2%E3%80%81XA%E6%A8%A1%E5%BC%8F">3.5.2、XA 模式</a></p>
<p><a href="#3.5.3%E3%80%81AT%E6%A8%A1%E5%BC%8F">3.5.3、AT 模式</a></p>
<p><a href="#%E5%9B%9B%E3%80%81%E6%A1%88%E4%BE%8B%E6%BC%94%E7%A4%BA%EF%BC%9A%E5%BE%AE%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8%E7%9A%84%E5%AE%8C%E6%95%B4%E6%B5%81%E7%A8%8B">4、案例演示：微服务调用的完整流程</a></p>
<p><a href="#%E4%BA%94%E3%80%81%E6%80%BB%E7%BB%93">5、结语</a></p>
<h2 id="1、引言"><a href="#1、引言" class="headerlink" title="1、引言"></a>1、引言</h2><blockquote>
<p>在当今的软件开发领域，微服务架构已经成为一种热门的设计模式。与传统的单体架构相比，微服务架构通过将系统拆分为多个小而独立的服务，使得开发、部署和维护变得更加灵活。这种架构设计不仅能够适应现代互联网应用的快速变化需求，还能在一定程度上缓解开发团队之间的协作冲突。<br>但同时，微服务也引入了许多新的挑战，其中最核心的一个问题就是服务之间的相互调用。在单体架构中，模块之间的调用通常是本地方法的调用，简单而高效。而在微服务架构中，不同服务运行在独立的进程中，甚至可能分布在不同的服务器上，服务之间的通信需要通过网络来实现，这无疑增加了复杂性。<br>在这篇文章中，我们将围绕微服务架构的基础概念展开，重点探讨其与单体项目的主要不同点，尤其是服务之间的调用逻辑。通过一个简单的案例，我们会展示 SpringCloud 框架下服务调用的完整流程，帮助你快速理解微服务。</p>
</blockquote>
<h2 id="2、微服务基础概念"><a href="#2、微服务基础概念" class="headerlink" title="2、微服务基础概念"></a>2、微服务基础概念</h2><h3 id="2-1、框架总览"><a href="#2-1、框架总览" class="headerlink" title="2.1、框架总览"></a>2.1、框架总览</h3><blockquote>
<p>首先要了解什么是微服务就需要对其整体框架有一个全局的概念，下面这张概述图详细的展示了微服务的大体框架：</p>
</blockquote>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_6c17275f.png" alt="pic_6c17275f.png"></p>
<blockquote>
<p>可以发现微服务涉及的相关组件还是蛮多的，相较于单体项目终端直接与数据库相连，中间最多加个网关，微服务由于其服务之间的隔离性，导致其相互调用成了一个比较麻烦的问题，随之而来的还有一大堆分布式事务相关的安全问题。</p>
</blockquote>
<blockquote>
<p>本文主要对控制面、治理面和数据面进行讲解，涉及到的组件有：</p>
<ul>
<li>Nacos</li>
<li>Sentinel</li>
<li>GateWay 网关</li>
<li>OpenFeign</li>
<li>RabbitMQ</li>
</ul>
</blockquote>
<h3 id="2-2、微服务与单体架构的对比"><a href="#2-2、微服务与单体架构的对比" class="headerlink" title="2.2、微服务与单体架构的对比"></a>2.2、微服务与单体架构的对比</h3><table> 
 <tbody> 
  <tr> 
   <td>特性</td> 
   <td>单体架构</td> 
   <td>微服务架构</td> 
  </tr> 
  <tr> 
   <td>开发</td> 
   <td>所有模块集中在一个代码库中，协作紧密</td> 
   <td>各模块独立开发，松耦合</td> 
  </tr> 
  <tr> 
   <td>部署</td> 
   <td>整体部署，一次更新影响整个系统</td> 
   <td>独立部署，每个服务可以单独更新</td> 
  </tr> 
  <tr> 
   <td>扩展</td> 
   <td>水平扩展整个应用，浪费资源</td> 
   <td>精确扩展特定服务，资源利用率更高</td> 
  </tr> 
  <tr> 
   <td>技术栈</td> 
   <td>单一技术栈，统一性强</td> 
   <td>多样化技术栈，根据服务需求灵活选择</td> 
  </tr> 
 </tbody> 
</table>

<h2 id="3、微服务的最大特点：服务之间的调用"><a href="#3、微服务的最大特点：服务之间的调用" class="headerlink" title="3、微服务的最大特点：服务之间的调用"></a>3、微服务的最大特点：服务之间的调用</h2><blockquote>
<p>在微服务架构中，服务之间的调用是系统运行的核心，也是微服务架构与单体架构最大的区别之一。由于服务运行在不同的进程中，甚至分布在不同的服务器上，其通信方式需要考虑网络传输的复杂性。以下是微服务调用的几个关键要素以及相关组件：</p>
</blockquote>
<h3 id="3-1、服务注册"><a href="#3-1、服务注册" class="headerlink" title="3.1、服务注册"></a>3.1、服务注册</h3><blockquote>
<p>在微服务架构中，每个服务可能动态变化（如启动、关闭或迁移）。为了实现服务之间的通信，首先需要一个机制来注册和发现服务。</p>
</blockquote>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_ee9bc610.png" alt="pic_ee9bc610.png"></p>
<h4 id="3-1-1、注册中心原理"><a href="#3-1-1、注册中心原理" class="headerlink" title="3.1.1、注册中心原理"></a>3.1.1、注册中心原理</h4><blockquote>
<p>举个例子，在微服务远程调用的过程中，包括两个角色：</p>
<ul>
<li>服务提供者：提供接口供其它微服务访问，比如 <code>item-service</code></li>
<li>服务消费者：调用其它微服务提供的接口，比如 <code>cart-service</code></li>
</ul>
</blockquote>
<blockquote>
<p>在大型微服务项目中，服务提供者的数量会非常多，为了管理这些服务就引入了注册中心的概念。注册中心、服务提供者、服务消费者三者间关系如下：</p>
</blockquote>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_e4133fd6.png" alt="pic_e4133fd6.png"></p>
<blockquote>
<p>流程如下：</p>
<ul>
<li>服务启动时就会注册自己的服务信息（服务名、IP、端口）到注册中心</li>
<li>调用者可以从注册中心订阅想要的服务，获取服务对应的实例列表（1 个服务可能多实例部署）</li>
<li>调用者自己对实例列表负载均衡，挑选一个实例</li>
<li>调用者向该实例发起远程调用</li>
</ul>
</blockquote>
<blockquote>
<p>当服务提供者的实例宕机或者启动新实例时，调用者如何得知呢？</p>
<ul>
<li>服务提供者会定期向注册中心发送请求，报告自己的健康状态（心跳请求）</li>
<li>当注册中心长时间收不到提供者的心跳时，会认为该实例宕机，将其从服务的实例列表中剔除</li>
<li>当服务有新实例启动时，会发送注册服务请求，其信息会被记录在注册中心的服务实例列表</li>
<li>当注册中心服务列表变更时，会主动通知微服务，更新本地服务列表</li>
</ul>
</blockquote>
<h4 id="3-1-2、Nacos-注册中心"><a href="#3-1-2、Nacos-注册中心" class="headerlink" title="3.1.2、Nacos 注册中心"></a>3.1.2、Nacos 注册中心</h4><blockquote>
<p>目前开源的注册中心框架有很多，国内比较常见的有：</p>
<ul>
<li>Eureka：Netflix 公司出品，目前被集成在 SpringCloud 当中，一般用于 Java 应用</li>
<li>Nacos：Alibaba 公司出品，目前被集成在 SpringCloudAlibaba 中，一般用于 Java 应用</li>
<li>Consul：HashiCorp 公司出品，目前集成在 SpringCloud 中，不限制微服务语言</li>
</ul>
</blockquote>
<blockquote>
<p>由于 Nacos 是国内产品，中文文档比较丰富，而且同时具备配置管理功能，所以使用较多。下面来讲一下怎么部署 Nacos，推荐是使用 linux 虚拟机结合 docker 来进行部署，这里就不详细演示怎么安装 docker 了，有需要的可以去看一下我之前写的博客，这里就直接参考 Nacos 官网进行安装：</p>
</blockquote>
<p><a target="_blank" rel="noopener" href="https://nacos.io/docs/latest/quickstart/quick-start">Nacos 快速开始 | Nacos 官网这个快速开始手册是帮忙您快速在您的电脑上，下载、安装并使用 Nacos。https://nacos.io/docs/latest/quickstart/quick-start<img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_6feeb0fa.png" alt="pic_6feeb0fa.png">https://nacos.io/docs/latest/quickstart/quick-start</a><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_e8d7d828.png" alt="pic_e8d7d828.png"></p>
<p>解压到非中文路径下：</p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_397574c9.png" alt="pic_397574c9.png"></p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_1dbd4962.png" alt="pic_1dbd4962.png"></p>
<p>打开编辑，在开头处加入如下内容:</p>
<figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Copyright 1999-2018 Alibaba Group Holding Ltd.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);</span></span><br><span class="line"><span class="comment"> * you may not use this file except in compliance with the License.</span></span><br><span class="line"><span class="comment"> * You may obtain a copy of the License at</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> *      http://www.apache.org/licenses/LICENSE-2.0</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * Unless required by applicable law or agreed to in writing, software</span></span><br><span class="line"><span class="comment"> * distributed under the License is distributed on an &quot;AS IS&quot; BASIS,</span></span><br><span class="line"><span class="comment"> * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.</span></span><br><span class="line"><span class="comment"> * See the License for the specific language governing permissions and</span></span><br><span class="line"><span class="comment"> * limitations under the License.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> # 以下内容需手动加入</span><br><span class="line"><span class="keyword">DROP</span> DATABASE IF <span class="keyword">EXISTS</span> `nacos_config`;</span><br><span class="line"><span class="keyword">CREATE</span> DATABASE `nacos_config` <span class="keyword">DEFAULT</span> <span class="keyword">character set</span> utf8mb4;</span><br><span class="line"><span class="keyword">SET</span> names utf8mb4;</span><br><span class="line"><span class="keyword">SET</span> FOREIGN_KEY_CHECKS <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">USE `nacos_config`;</span><br><span class="line"></span><br><span class="line"><span class="comment">/******************************************/</span></span><br><span class="line"><span class="comment">/*   数据库全名 = nacos_config   */</span></span><br><span class="line"><span class="comment">/*   表名称 = config_info   */</span></span><br><span class="line"><span class="comment">/******************************************/</span></span><br></pre></td></tr></table></figure>

<p>然后打开你的数据库将脚本导入创建 nacos 数据库：</p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_06de63c9.png" alt="pic_06de63c9.png"></p>
<p>修改数据库连接默认配置：</p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_28c31890.png" alt="pic_28c31890.png"></p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_a698e5e1.png" alt="pic_a698e5e1.png"></p>
<p>启动后访问 localhost:8848&#x2F;nacos ：</p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_98c3381a.png" alt="pic_98c3381a.png"></p>
<h4 id="3-1-3、服务注册"><a href="#3-1-3、服务注册" class="headerlink" title="3.1.3、服务注册"></a>3.1.3、服务注册</h4><blockquote>
<p>接下来就是在你的项目中添加依赖并进行配置：</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">&lt;!--nacos 服务注册发现--&gt;</span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;com.alibaba.cloud&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;spring-cloud-starter-alibaba-nacos-discovery&lt;/artifactId&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">spring:</span><br><span class="line">  application:</span><br><span class="line">    name: item-service # 服务名称</span><br><span class="line">  cloud:</span><br><span class="line">    nacos:</span><br><span class="line">      server-addr: <span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">8848</span> # nacos地址</span><br></pre></td></tr></table></figure>

<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_6b15c275.png" alt="pic_6b15c275.png"></p>
<h3 id="3-2、服务间通信方式"><a href="#3-2、服务间通信方式" class="headerlink" title="3.2、服务间通信方式"></a>3.2、服务间通信方式</h3><blockquote>
<p>微服务之间的通信主要有以下两种方式：</p>
<ul>
<li>同步调用（HTTP REST）：使用轻量级的 HTTP 协议，适合请求-响应模式。SpringCloud 提供了 OpenFeign 组件用于简化 REST 调用，通过声明式接口实现服务间的远程调用。</li>
<li>异步调用（消息队列）：通过消息中间件实现异步通信和解耦，适合事件驱动的场景。RabbitMQ：一个流行的开源消息队列，支持 AMQP 协议，具有高性能、可靠性和灵活性，常用于实现微服务间的异步通信和事件通知。</li>
</ul>
</blockquote>
<h4 id="3-2-1、OpenFeign"><a href="#3-2-1、OpenFeign" class="headerlink" title="3.2.1、OpenFeign"></a>3.2.1、OpenFeign</h4><blockquote>
<p>其实远程调用的关键点就在于四个：</p>
<ol>
<li>请求方式</li>
<li>请求路径</li>
<li>请求参数</li>
<li>返回值类型</li>
</ol>
</blockquote>
<blockquote>
<p>所以，OpenFeign 就利用 SpringMVC 的相关注解来声明上述 4 个参数，然后基于动态代理帮我们生成远程调用的代码，而无需我们手动再编写，非常方便。</p>
<p>需要注意的是，调用方也要像刚才那样引入 nacos 依赖并进行配置，接下来，我们就通过一个快速入门的案例来体验一下 OpenFeign 的便捷吧。</p>
</blockquote>
<p>首先还是在调用方引入依赖：</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">&lt;!--openFeign--&gt;</span><br><span class="line">  &lt;dependency&gt;</span><br><span class="line">      &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;</span><br><span class="line">      &lt;artifactId&gt;spring-cloud-starter-openfeign&lt;/artifactId&gt;</span><br><span class="line">  &lt;/dependency&gt;</span><br><span class="line">  &lt;!--负载均衡器--&gt;</span><br><span class="line">  &lt;dependency&gt;</span><br><span class="line">      &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;</span><br><span class="line">      &lt;artifactId&gt;spring-cloud-starter-loadbalancer&lt;/artifactId&gt;</span><br><span class="line">  &lt;/dependency&gt;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>注意这里服务之间的调用涉及到了负载均衡，所以也要把 loadbalancer 的依赖也引入，关于负载均衡的讲解放到了后面。</p>
</blockquote>
<p>在调用者的启动类上添加注解，启动 OpenFeign 功能：</p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_c4a34833.png" alt="pic_c4a34833.png"></p>
<p>然后新建一个 client 包，里面放访问客户端：</p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_6c587f75.png" alt="pic_6c587f75.png"></p>
<blockquote>
<p>这里只需要声明接口，无需实现方法，因为 OpenFeign 基于 Nacos 的远程调用原理是 声明式 HTTP 调用 + 动态服务发现，通过动态代理、Nacos 的服务注册与发现，以及负载均衡等机制实现服务之间的无缝通信。具体流程后面会讲到。</p>
</blockquote>
<blockquote>
<p>接口中的几个关键信息：</p>
<ul>
<li>@FeignClient(“item-service”) ：声明服务名称</li>
<li>@GetMapping ：声明请求方式</li>
<li>@GetMapping(“&#x2F;items”) ：声明请求路径</li>
<li>@RequestParam(“ids”) Collection<Long> ids ：声明请求参数</li>
<li>List<ItemDTO> ：返回值类型</li>
</ul>
<p>有了上述信息，OpenFeign 就可以利用动态代理帮我们实现这个方法，并且向 <a target="_blank" rel="noopener" href="http://item-service/items">http://item-service/items</a> 发送一个 GET 请求，携带 ids 为请求参数，并自动将返回值处理为 List<ItemDTO>。</p>
<p>我们只需要直接调用这个方法，即可实现远程调用了。</p>
</blockquote>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_53b9572b.png" alt="pic_53b9572b.png"></p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_aee1db5c.png" alt="pic_aee1db5c.png"></p>
<blockquote>
<p>当然涉及到多线程必然要考虑性能的问题，OpenFeign 也支持线程池，在调用者端引入依赖并进行配置即可生效还是非常方便的。</p>
</blockquote>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line">#.xml</span><br><span class="line"><span class="comment">&lt;!--OK http 的依赖 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>io.github.openfeign<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>feign-okhttp<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>

<figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment">#.yaml</span></span><br><span class="line"><span class="attr">feign:</span></span><br><span class="line">  <span class="attr">okhttp:</span></span><br><span class="line">    <span class="attr">enabled:</span> <span class="literal">true</span> <span class="comment"># 开启OKHttp功能</span></span><br></pre></td></tr></table></figure>

<h4 id="3-2-2、RabbitMQ"><a href="#3-2-2、RabbitMQ" class="headerlink" title="3.2.2、RabbitMQ"></a>3.2.2、RabbitMQ</h4><blockquote>
<p>前面我们了解了同步调用，在讲解异步调用之前，首先还是来看一下它们各自的优缺点：</p>
</blockquote>
<table> 
 <tbody> 
  <tr> 
   <td>特性</td> 
   <td>同步调用</td> 
   <td>异步调用</td> 
  </tr> 
  <tr> 
   <td>定义</td> 
   <td>调用方发出请求后会等待响应完成，才能继续执行后续逻辑。</td> 
   <td>调用方发出请求后无需等待响应，可立即继续执行其他任务。</td> 
  </tr> 
  <tr> 
   <td>通信协议</td> 
   <td>通常基于 HTTP 或 RPC</td> 
   <td>通常基于消息队列（如 RabbitMQ、Kafka 等）。</td> 
  </tr> 
  <tr> 
   <td>耦合度</td> 
   <td>服务间紧密耦合，调用方需要直接知道服务提供方的信息。</td> 
   <td>服务间松耦合，通过消息中间件解耦。</td> 
  </tr> 
  <tr> 
   <td>实时性</td> 
   <td>响应实时性高，适合对结果有即时需求的场景。</td> 
   <td>延迟容忍度高，适合对结果实时性要求不高的场景。</td> 
  </tr> 
  <tr> 
   <td>可靠性</td> 
   <td>如果服务不可用，调用会失败，需要结合重试机制或熔断处理。</td> 
   <td>消息通常持久化存储，可靠性高，即使目标服务暂时不可用，消息不会丢失。</td> 
  </tr> 
 </tbody> 
</table>

<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_2b9a1a0f.png" alt="pic_2b9a1a0f.png"></p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_4b9baabb.png" alt="pic_4b9baabb.png"></p>
<blockquote>
<p>异步调用方式其实就是基于消息通知的方式，一般包含三个角色：</p>
<ol>
<li>消息发送者：投递消息的人，就是原来的调用方</li>
<li>消息 Broker：管理、暂存、转发消息，你可以把它理解成微信服务器</li>
<li>消息接收者：接收和处理消息的人，就是原来的服务提供方</li>
</ol>
</blockquote>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_1c3d2652.png" alt="pic_1c3d2652.png"></p>
<blockquote>
<p>在异步调用中，发送者不再直接同步调用接收者的业务接口，而是发送一条消息投递给消息 Broker。然后接收者根据自己的需求从消息 Broker 那里订阅消息。每当发送方发送消息后，接受者都能获取消息并处理。这样，发送消息的人和接收消息的人就完全解耦了。</p>
</blockquote>
<blockquote>
<p>核心思想就是只将关键服务同步调用，而其他一些不太重要的就交给 Broker 异步调用！</p>
</blockquote>
<blockquote>
<p>当然，异步通信也并非完美无缺，它存在下列缺点：</p>
<ul>
<li>完全依赖于 Broker 的可靠性、安全性和性能</li>
<li>架构复杂，后期维护和调试麻烦</li>
</ul>
</blockquote>
<blockquote>
<p>安装过程这里就不演示了，推荐使用 docker 容器隔离环境，登录后界面如图：</p>
</blockquote>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_0b08feb6.png" alt="pic_0b08feb6.png"></p>
<p>RabbitMQ 对应的架构如图：</p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_b98c3ace.png" alt="pic_b98c3ace.png"></p>
<blockquote>
<p>其中包含几个概念：</p>
<ul>
<li>publisher：生产者，也就是发送消息的一方</li>
<li>consumer：消费者，也就是消费消息的一方</li>
<li>queue：队列，存储消息。生产者投递的消息会暂存在消息队列中，等待消费者处理</li>
<li>exchange：交换机，负责消息路由。生产者发送的消息由交换机决定投递到哪个队列。</li>
<li>virtual host：虚拟主机，起到数据隔离的作用。每个虚拟主机相互独立，有各自的 exchange、queue(有点像 mysql 里的 database，不同的项目的 database 相互隔离)</li>
</ul>
<p>上述这些东西都可以在 RabbitMQ 的管理控制台来管理.</p>
</blockquote>
<blockquote>
<p>其中交换机和队列要建立绑定关系后才能传递信息，这个时候如果有消费者监听了 MQ 的队列，自然就能收到消息了。</p>
</blockquote>
<h4 id="3-2-3、SpringAMQP"><a href="#3-2-3、SpringAMQP" class="headerlink" title="3.2.3、SpringAMQP"></a>3.2.3、SpringAMQP</h4><blockquote>
<p>将来我们开发业务功能的时候，肯定不会在控制台收发消息，而是应该基于编程的方式。由于 RabbitMQ 采用了 AMQP 协议，因此它具备跨语言的特性。任何语言只要遵循 AMQP 协议收发消息，都可以与 RabbitMQ 交互。并且 RabbitMQ 官方也提供了各种不同语言的客户端。</p>
<p>但是，RabbitMQ 官方提供的 Java 客户端编码相对复杂，一般生产环境下我们更多会结合 Spring 来使用。而 Spring 的官方刚好基于 RabbitMQ 提供了这样一套消息收发的模板工具：SpringAMQP。并且还基于 SpringBoot 对其实现了自动装配，使用起来非常方便。</p>
</blockquote>
<blockquote>
<p>SpringAMQP 提供了三个功能：</p>
<ol>
<li>自动声明队列、交换机及其绑定关系</li>
<li>基于注解的监听器模式，异步接收消息</li>
<li>封装了 RabbitTemplate 工具，用于发送消息</li>
</ol>
</blockquote>
<p>SpringAMQP 如何收发消息？</p>
<p>1、引入 spring-boot-starter-amqp 依赖</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="comment">&lt;!--AMQP依赖，包含RabbitMQ--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-amqp<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>2、配置 rabbitmq 服务端信息</p>
<figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">rabbitmq:</span></span><br><span class="line">    <span class="attr">host:</span> <span class="number">192.168</span><span class="number">.36</span><span class="number">.8</span> <span class="comment"># 你的虚拟机IP</span></span><br><span class="line">    <span class="attr">port:</span> <span class="number">5672</span> <span class="comment"># 端口</span></span><br><span class="line">    <span class="attr">virtual-host:</span> <span class="string">/hmall</span> <span class="comment"># 虚拟主机</span></span><br><span class="line">    <span class="attr">username:</span> <span class="string">test</span> <span class="comment"># 用户名</span></span><br><span class="line">    <span class="attr">password:</span> <span class="number">123</span> <span class="comment"># 密码</span></span><br></pre></td></tr></table></figure>

<p>3、利用 RabbitTemplate 发送消息</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RabbitTemplate rabbitTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testSimpleQueue</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 队列名称</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">queueName</span> <span class="operator">=</span> <span class="string">&quot;simple.queue&quot;</span>;</span><br><span class="line">        <span class="comment">// 消息</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">message</span> <span class="operator">=</span> <span class="string">&quot;hello, spring amqp!&quot;</span>;</span><br><span class="line">        <span class="comment">// 发送消息</span></span><br><span class="line">        rabbitTemplate.convertAndSend(queueName, message);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<p>4、利用@RabbitListener 注解声明要监听的队列，监听消息</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 利用RabbitListener来声明要监听的队列信息</span></span><br><span class="line">    <span class="comment">// 将来一旦监听的队列中有了消息，就会推送给当前服务，调用当前方法，处理消息。</span></span><br><span class="line">    <span class="comment">// 可以看到方法体中接收的就是消息体的内容</span></span><br><span class="line">    <span class="meta">@RabbitListener(queues = &quot;simple.queue&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">listenSimpleQueueMessage</span><span class="params">(String msg)</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;spring 消费者接收到消息：【&quot;</span> + msg + <span class="string">&quot;】&quot;</span>);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_62994edd.png" alt="pic_62994edd.png"></p>
<blockquote>
<p>当消息处理比较耗时的时候，可能生产消息的速度会远远大于消息的消费速度。长此以往，消息就会堆积越来越多，无法及时处理。此时就可以使用 work 模型，多个消费者共同处理消息处理，消息处理的速度就能大大提高了，简单来说就是让多个消费者绑定到一个队列，共同消费队列中的消息。</p>
</blockquote>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_94df481c.png" alt="pic_94df481c.png"></p>
<blockquote>
<p>交换机的类型有很多其特点也是各不相同，不仅如此关于 RabbitMQ 还有很多的内容，像如何确保 MQ 消息的可靠性，以及消息发送失败后的处理方案和延迟消息，这里就不详细展开了。</p>
</blockquote>
<h3 id="3-3、负载均衡"><a href="#3-3、负载均衡" class="headerlink" title="3.3、负载均衡"></a>3.3、负载均衡</h3><blockquote>
<p>我们知道微服务间远程同步调用都是由 OpenFeign 帮我们完成的，甚至帮我们实现了服务列表之间的负载均衡。但具体负载均衡的规则是什么呢？何时做的负载均衡呢？</p>
<p>接下来我们一起来分析一下。</p>
</blockquote>
<h4 id="3-3-1、源码跟踪"><a href="#3-3-1、源码跟踪" class="headerlink" title="3.3.1、源码跟踪"></a>3.3.1、源码跟踪</h4><p>首先来梳理一下远程调用的步骤：</p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_5cf6fee9.png" alt="pic_5cf6fee9.png"></p>
<p>整体流程如下图所示：</p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_4f5eba2e.png" alt="pic_4f5eba2e.png"></p>
<blockquote>
<p>可以看到 <code>FeignBlockingLoadBalancerClient</code> 是一个适配器，内部使用了 <code>LoadBalancerClient</code> 来实现服务实例的选择和请求的负载均衡处理。</p>
</blockquote>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_1e244f9f.png" alt="pic_1e244f9f.png"></p>
<p>我们跟进去发现 <code>LoadBalancerClient 接口</code>只有一个实现类就是 BlockingLoadBalancerClient：</p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_855654a2.png" alt="pic_855654a2.png"></p>
<p>其中的 choose 方法实现了负载均衡：</p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_185d4835.png" alt="pic_185d4835.png"></p>
<p>我们继续跟进：</p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_45dbc134.png" alt="pic_45dbc134.png"></p>
<blockquote>
<p>ReactiveLoadBalancer 是 Spring-Cloud-Common 组件中定义的负载均衡器接口规范，而 Spring-Cloud-Loadbalancer 组件给出了两个实现：</p>
</blockquote>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_a9d3ab98.png" alt="pic_a9d3ab98.png"></p>
<p>默认的实现是 RoundRobinLoadBalancer，即轮询负载均衡器。负载均衡器的核心逻辑如下：</p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_a7f74d43.png" alt="pic_a7f74d43.png"></p>
<p>这里的 ServiceInstanceListSupplier（服务拉取）也有很多实现：</p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_227eaadd.png" alt="pic_227eaadd.png"></p>
<blockquote>
<p>其中 CachingServiceInstanceListSupplier 采用了装饰模式，加了服务实例列表缓存，避免每次都要去注册中心拉取服务实例列表。而其内部是基于<code>DiscoveryClientServiceInstanceListSupplier</code>来实现的。</p>
<p>在这个类的构造函数中，就会异步的基于 DiscoveryClient 去拉取服务的实例列表：</p>
</blockquote>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_3f787f7a.png" alt="pic_3f787f7a.png"></p>
<h4 id="3-3-2、NacosRule"><a href="#3-3-2、NacosRule" class="headerlink" title="3.3.2、NacosRule"></a>3.3.2、NacosRule</h4><blockquote>
<p>之前分析源码的时候我们发现负载均衡的算法是有<code>ReactiveLoadBalancer</code>来定义的，我们发现它的实现类有三个：</p>
</blockquote>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_bb776c1b.png" alt="pic_bb776c1b.png"></p>
<blockquote>
<p>其中<code>RoundRobinLoadBalancer</code>和<code>RandomLoadBalancer</code>是由<code>Spring-Cloud-Loadbalancer</code>模块提供的，而<code>NacosLoadBalancer</code>则是由<code>Nacos-Discorvery</code>模块提供的。</p>
<p>默认采用的负载均衡策略是<code>RoundRobinLoadBalancer</code>，那如果我们要切换负载均衡策略该怎么办？</p>
</blockquote>
<p>查看源码会发现，<code>Spring-Cloud-Loadbalancer</code>模块中有一个自动配置类：</p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_f17d0a34.png" alt="pic_f17d0a34.png"></p>
<p>其中定义了默认的负载均衡器：</p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_3d5b50d2.png" alt="pic_3d5b50d2.png"></p>
<blockquote>
<p>这个 Bean 上添加了<code>@ConditionalOnMissingBean</code>注解，也就是说如果我们自定义了这个类型的 bean，则负载均衡的策略就会被改变。</p>
<p>这个配置类千万不要加@Configuration 注解，也不要被 SpringBootApplication 扫描到。</p>
</blockquote>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_88dcedb4.png" alt="pic_88dcedb4.png"></p>
<blockquote>
<p>由于这个 OpenFeignConfig 没有加@Configuration 注解，也就没有被 Spring 加载，因此是不会生效的。接下来，我们要在启动类上通过注解来声明这个配置。</p>
</blockquote>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_0c5b3c7c.png" alt="pic_0c5b3c7c.png"></p>
<blockquote>
<p><code>RoundRobinLoadBalancer</code>是轮询算法，<code>RandomLoadBalancer</code>是随机算法，那么<code>NacosLoadBalancer</code>是什么负载均衡算法呢？</p>
</blockquote>
<blockquote>
<p>简单来说<code>NacosLoadBalancer</code>是一个基于权重的加权平均算法，我们打开 nacos 控制台，进入<code>item-service</code>的服务详情页，可以看到每个实例后面都有一个编辑按钮，在这里就可以修改每个服务的权重了：</p>
</blockquote>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_b1638609.png" alt="pic_b1638609.png"></p>
<h3 id="3-4、-服务容错机制"><a href="#3-4、-服务容错机制" class="headerlink" title="3.4、 服务容错机制"></a>3.4、 服务容错机制</h3><blockquote>
<p>由于微服务通信依赖于网络，可能会出现超时、失败等问题。服务容错机制可以保证系统的稳定性。</p>
</blockquote>
<h4 id="3-4-1、服务保护方案"><a href="#3-4-1、服务保护方案" class="headerlink" title="3.4.1、服务保护方案"></a>3.4.1、服务保护方案</h4><blockquote>
<p>微服务保护的方案有很多，比如：</p>
<ul>
<li>请求限流</li>
<li>线程隔离</li>
<li>服务熔断</li>
</ul>
</blockquote>
<blockquote>
<p>这些方案或多或少都会导致服务的体验上略有下降，比如请求限流，降低了并发上限；线程隔离，降低了可用资源数量；服务熔断，降低了服务的完整度，部分服务变的不可用或弱可用。因此这些方案都属于服务降级的方案。但通过这些方案，服务的健壮性得到了提升，接下来，我们就逐一了解这些方案的原理。</p>
</blockquote>
<p>请求限流</p>
<blockquote>
<p>服务故障最重要原因，就是并发太高！解决了这个问题，就能避免大部分故障。当然，接口的并发不是一直很高，而是突发的。因此请求限流，就是限制或控制接口访问的并发流量，避免服务因流量激增而出现故障。</p>
<p>请求限流往往会有一个限流器，数量高低起伏的并发请求曲线，经过限流器就变的非常平稳。这就像是水电站的大坝，起到蓄水的作用，可以通过开关控制水流出的大小，让下游水流始终维持在一个平稳的量。</p>
</blockquote>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_5dcdac23.png" alt="pic_5dcdac23.png"></p>
<p>线程隔离</p>
<blockquote>
<p>当一个业务接口响应时间长，而且并发高时，就可能耗尽服务器的线程资源，导致服务内的其它接口受到影响。所以我们必须把这种影响降低，或者缩减影响的范围。线程隔离正是解决这个问题的好办法。</p>
</blockquote>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_7a459a49.png" alt="pic_7a459a49.png"></p>
<p>服务熔断</p>
<blockquote>
<p>线程隔离虽然避免了雪崩问题，但故障服务依然会拖慢服务调用方的接口响应速度，这个时候就需要进行熔断以拒绝调用该接口。</p>
</blockquote>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_3f36eb3c.png" alt="pic_3f36eb3c.png"></p>
<h4 id="3-4-2、Sentinel"><a href="#3-4-2、Sentinel" class="headerlink" title="3.4.2、Sentinel"></a>3.4.2、Sentinel</h4><blockquote>
<p>Sentinel 是阿里巴巴开源的一款 分布式系统流量防护组件，主要用于实现流量控制（Flow Control）、熔断降级（Circuit Breaking）和系统自适应保护（System Adaptive Protection）等功能，是保障微服务系统稳定性的重要工具。</p>
</blockquote>
<p>下载地址：</p>
<p><a target="_blank" rel="noopener" href="https://github.com/alibaba/Sentinel/releases">Releases · alibaba&#x2F;Sentinel (github.com)https://github.com/alibaba/Sentinel/releases<img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_da463f82.png" alt="pic_da463f82.png">https://github.com/alibaba/Sentinel/releases</a>然后运行如下命令启动控制台：</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">java -Dserver.port=<span class="number">8090</span> -Dcsp.sentinel.dashboard.server=localhost:<span class="number">8090</span> -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar</span><br></pre></td></tr></table></figure>

<p>建议是写成.bat 文件然后双击启动：</p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_74b39e89.png" alt="pic_74b39e89.png"></p>
<p>访问<a target="_blank" rel="noopener" href="http://localhost:8080/">http://localhost:8090</a>页面，就可以看到 sentinel 的控制台了：</p>
<p>需要输入账号和密码，默认都是：sentinel</p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_4fbc2f32.png" alt="pic_4fbc2f32.png"></p>
<blockquote>
<p>然后就是整合到你的项目中去，引入依赖并修改配置文件</p>
</blockquote>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="comment">&lt;!--sentinel--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.alibaba.cloud<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-cloud-starter-alibaba-sentinel<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>

<figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">cloud:</span></span><br><span class="line">    <span class="attr">sentinel:</span></span><br><span class="line">      <span class="attr">transport:</span></span><br><span class="line">        <span class="attr">dashboard:</span> <span class="string">localhost:8090</span></span><br><span class="line">      <span class="attr">http-method-specify:</span> <span class="literal">true</span> <span class="comment"># 开启请求方式前缀</span></span><br></pre></td></tr></table></figure>

<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_1681c221.png" alt="pic_1681c221.png"></p>
<h4 id="3-4-3、请求限流"><a href="#3-4-3、请求限流" class="headerlink" title="3.4.3、请求限流"></a>3.4.3、请求限流</h4><p>直接在 sentinel 设置就行：</p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_6b3f9dfd.png" alt="pic_6b3f9dfd.png"></p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_ec3ef67d.png" alt="pic_ec3ef67d.png"></p>
<h4 id="3-4-4、线程隔离"><a href="#3-4-4、线程隔离" class="headerlink" title="3.4.4、线程隔离"></a>3.4.4、线程隔离</h4><p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_e9f1eede.png" alt="pic_e9f1eede.png"></p>
<blockquote>
<p>总的处理能力等于并发线程数乘上单机 QPS</p>
</blockquote>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_daf71bfb.png" alt="pic_daf71bfb.png"></p>
<h4 id="3-4-5、服务熔断"><a href="#3-4-5、服务熔断" class="headerlink" title="3.4.5、服务熔断"></a>3.4.5、服务熔断</h4><p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_31fb73f1.png" alt="pic_31fb73f1.png"></p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_f33fbd76.png" alt="pic_f33fbd76.png"></p>
<h3 id="3-5、分布式事务"><a href="#3-5、分布式事务" class="headerlink" title="3.5、分布式事务"></a>3.5、分布式事务</h3><h4 id="3-5-1、Seata"><a href="#3-5-1、Seata" class="headerlink" title="3.5.1、Seata"></a>3.5.1、Seata</h4><blockquote>
<p>解决分布式事务的方案有很多，但实现起来都比较复杂，因此我们一般会使用开源的框架来解决分布式事务问题。在众多的开源分布式事务框架中，功能最完善、使用最多的就是阿里巴巴在 2019 年开源的 Seata 了。</p>
</blockquote>
<p><a target="_blank" rel="noopener" href="https://seata.apache.org/zh-cn/docs/overview/what-is-seata/">Seata 是什么？ | Apache Seatahttps://seata.apache.org/zh-cn/docs/overview/what-is-seata/<img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_f30f1de4.png" alt="pic_f30f1de4.png">https://seata.apache.org/zh-cn/docs/overview/what-is-seata/</a></p>
<blockquote>
<p>其实分布式事务产生的一个重要原因，就是参与事务的多个分支事务互相无感知，不知道彼此的执行状态。因此解决分布式事务的思想非常简单：</p>
<p>就是找一个统一的事务协调者，与多个分支事务通信，检测每个分支事务的执行状态，保证全局事务下的每一个分支事务同时成功或失败即可。大多数的分布式事务框架都是基于这个理论来实现的。</p>
</blockquote>
<blockquote>
<p>Seata 也不例外，在 Seata 的事务管理中有三个重要的角色：</p>
<ol>
<li>TC (Transaction Coordinator) - 事务协调者：维护全局和分支事务的状态，协调全局事务提交或回滚。</li>
<li>TM (Transaction Manager) - 事务管理器：定义全局事务的范围、开始全局事务、提交或回滚全局事务。</li>
<li>RM (Resource Manager) - 资源管理器：管理分支事务，与 TC 交谈以注册分支事务和报告分支事务的状态，并驱动分支事务提交或回滚。</li>
</ol>
</blockquote>
<p>Seata 的工作架构如图所示：</p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_e60aa4cf.png" alt="pic_e60aa4cf.png"></p>
<blockquote>
<p>其中，TM 和 RM 可以理解为 Seata 的客户端部分，引入到参与事务的微服务依赖中即可。将来 TM 和 RM 就会协助微服务，实现本地分支事务与 TC 之间交互，实现事务的提交或回滚。而 TC 服务则是事务协调中心，是一个独立的微服务，需要单独部署。</p>
</blockquote>
<blockquote>
<p>将 seata 部署然后访问（需要注意，要确保 nacos、mysql 都在同一个网络中）：</p>
</blockquote>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_d07a654e.png" alt="pic_d07a654e.png"></p>
<p>微服务集成 Seata</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="comment">&lt;!--seata--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.alibaba.cloud<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-cloud-starter-alibaba-seata<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>

<figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">seata:</span></span><br><span class="line">  <span class="attr">registry:</span> <span class="comment"># TC服务注册中心的配置，微服务根据这些信息去注册中心获取tc服务地址</span></span><br><span class="line">    <span class="attr">type:</span> <span class="string">nacos</span> <span class="comment"># 注册中心类型 nacos</span></span><br><span class="line">    <span class="attr">nacos:</span></span><br><span class="line">      <span class="attr">server-addr:</span> <span class="number">192.168</span><span class="number">.36</span><span class="number">.8</span><span class="string">:8848</span> <span class="comment"># nacos地址</span></span><br><span class="line">      <span class="attr">namespace:</span> <span class="string">&quot;&quot;</span> <span class="comment"># namespace，默认为空</span></span><br><span class="line">      <span class="attr">group:</span> <span class="string">DEFAULT_GROUP</span> <span class="comment"># 分组，默认是DEFAULT_GROUP</span></span><br><span class="line">      <span class="attr">application:</span> <span class="string">seata-server</span> <span class="comment"># seata服务名称</span></span><br><span class="line">      <span class="attr">username:</span> <span class="string">nacos</span></span><br><span class="line">      <span class="attr">password:</span> <span class="string">nacos</span></span><br><span class="line">  <span class="attr">tx-service-group:</span> <span class="string">hmall</span> <span class="comment"># 事务组名称</span></span><br><span class="line">  <span class="attr">service:</span></span><br><span class="line">    <span class="attr">vgroup-mapping:</span> <span class="comment"># 事务组与tc集群的映射关系</span></span><br><span class="line">      <span class="attr">hmall:</span> <span class="string">&quot;default&quot;</span></span><br><span class="line">  <span class="attr">data-source-proxy-mode:</span> <span class="string">XA</span></span><br></pre></td></tr></table></figure>

<h4 id="3-5-2、XA-模式"><a href="#3-5-2、XA-模式" class="headerlink" title="3.5.2、XA 模式"></a>3.5.2、XA 模式</h4><p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_e6d9217e.png" alt="pic_e6d9217e.png"></p>
<blockquote>
<p>RM 一阶段的工作：</p>
<ul>
<li>注册分支事务到 TC</li>
<li>执行分支业务 sql 但不提交</li>
<li>报告执行状态到 TC</li>
</ul>
<p>TC 二阶段的工作：</p>
<p>TC 检测各分支事务执行状态</p>
<ul>
<li>如果都成功，通知所有 RM 提交事务</li>
<li>如果有失败，通知所有 RM 回滚事务</li>
</ul>
<p>RM 二阶段的工作：</p>
<ul>
<li>接收 TC 指令，提交或回滚事务</li>
</ul>
</blockquote>
<p>进行测试，可以发现事务成功回滚：</p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_94cbcb7b.png" alt="pic_94cbcb7b.png"></p>
<blockquote>
<p>XA 模式的优点是什么？</p>
<ul>
<li>事务的强一致性，满足 ACID 原则</li>
<li>常用数据库都支持，实现简单，并且没有代码侵入</li>
</ul>
<p>XA 模式的缺点是什么？</p>
<ul>
<li>因为一阶段需要锁定数据库资源，等待二阶段结束才释放，性能较差</li>
<li>依赖关系型数据库实现事务</li>
</ul>
</blockquote>
<h4 id="3-5-3、AT-模式"><a href="#3-5-3、AT-模式" class="headerlink" title="3.5.3、AT 模式"></a>3.5.3、AT 模式</h4><blockquote>
<p>AT 模式同样是分阶段提交的事务模型，不过缺弥补了 XA 模型中资源锁定周期过长的缺陷。</p>
</blockquote>
<p>Seata 的 AT 模型：</p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_b8633d02.png" alt="pic_b8633d02.png"></p>
<blockquote>
<p>阶段一 RM 的工作：</p>
<ul>
<li>注册分支事务</li>
<li>记录 undo-log（数据快照）</li>
<li>执行业务 sql 并提交</li>
<li>报告事务状态</li>
</ul>
<p>阶段二提交时 RM 的工作：</p>
<ul>
<li>删除 undo-log 即可</li>
</ul>
<p>阶段二回滚时 RM 的工作：</p>
<ul>
<li>根据 undo-log 恢复数据到更新前</li>
</ul>
</blockquote>
<p>AT 模式与 XA 模式的最大区别：</p>
<table> 
 <tbody> 
  <tr> 
   <td>维度</td> 
   <td>AT 模式</td> 
   <td>XA 模式</td> 
  </tr> 
  <tr> 
   <td>锁定范围</td> 
   <td>业务逻辑层的状态，锁定时间短</td> 
   <td>数据库资源（行或表），锁定时间长</td> 
  </tr> 
  <tr> 
   <td>一致性</td> 
   <td>最终一致性，通过补偿机制实现</td> 
   <td>强一致性，依赖数据库的两阶段提交</td> 
  </tr> 
 </tbody> 
</table>

<h2 id="4、案例演示：微服务调用的完整流程"><a href="#4、案例演示：微服务调用的完整流程" class="headerlink" title="4、案例演示：微服务调用的完整流程"></a>4、案例演示：微服务调用的完整流程</h2><blockquote>
<p>这一部分我将通过消息队列对服务之间的异步调用进行演示，同时讲一下延迟消息的实现。</p>
</blockquote>
<p>先让我们来看一下什么是延迟消息：</p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_14547a3b.png" alt="pic_14547a3b.png"></p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_b350093e.png" alt="pic_b350093e.png"></p>
<blockquote>
<p>其中 ttl.queue 队列由于没用消费者监听，那么它的消息就会成为死信，我们将其绑定到对应的死信交换机上，这个时候如果由消费者来监听就能够成功接收消息，但是此时距离消息发送已经过去了一段时间，所以产生了延时。</p>
</blockquote>
<blockquote>
<p>但是！基于死信队列虽然可以实现延迟消息，但太麻烦了。因此 RabbitMQ 社区提供了一个延迟消息插件来实现相同的效果。</p>
</blockquote>
<p><a target="_blank" rel="noopener" href="https://github.com/rabbitmq/rabbitmq-delayed-message-exchange">rabbitmq&#x2F;rabbitmq-delayed-message-exchange: Delayed Messaging for RabbitMQ (github.com)https://github.com/rabbitmq/rabbitmq-delayed-message-exchange<img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_cd3d336a.png" alt="pic_cd3d336a.png">https://github.com/rabbitmq/rabbitmq-delayed-message-exchange</a>这样我们就能够使用注解的方式直接发送延时消息了：</p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_def6e004.png" alt="pic_def6e004.png"></p>
<blockquote>
<p>介绍完了延时消息，下面进行案例演示，以下是项目流程图：</p>
</blockquote>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_02afc096.png" alt="pic_02afc096.png"></p>
<blockquote>
<p>这里描述主要流程，涉及三个服务：订单服务、支付服务 和 商品服务。当用户提交订单后，系统将依次完成以下步骤：</p>
</blockquote>
<blockquote>
<ul>
<li>扣减库存：调用商品服务扣减对应商品的库存。</li>
<li>创建支付订单：订单服务创建支付订单，并跳转至支付服务。</li>
<li>支付处理：用户在支付服务中完成支付，支付成功后，支付服务通过消息通知订单服务支付成功。</li>
<li>消息丢失处理：如果支付服务长时间未向订单服务返回支付成功的消息（可能由于消息丢失），订单服务会通过延迟消息机制检测到异常。</li>
<li>关闭订单：订单服务将支付订单状态修改为 已关闭，并调用支付服务将支付状态同步为 已取消。</li>
<li>恢复库存：订单服务调用商品服务，恢复相应的商品库存。</li>
</ul>
</blockquote>
<blockquote>
<p>这样，通过延迟消息机制和状态回滚，系统能够有效应对消息丢失，确保最终一致性。</p>
</blockquote>
<p>改造下单业务，发送延迟消息：</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 5.发送延迟消息，检测订单支付状态</span></span><br><span class="line">rabbitTemplate.convertAndSend(</span><br><span class="line">    MQConstants.DELAY_EXCHANGE_NAME,</span><br><span class="line">    MQConstants.DELAY_ORDER_KEY,</span><br><span class="line">    order.getId(),</span><br><span class="line">    message -&gt; &#123;</span><br><span class="line">        message.getMessageProperties().setDelay(<span class="number">1800000</span>); <span class="comment">// 延时30分钟</span></span><br><span class="line">        <span class="keyword">return</span> message;</span><br><span class="line">    &#125;</span><br><span class="line">);</span><br></pre></td></tr></table></figure>

<p>这里将 交换机 和 Routing key 写成了一个常量类：</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">MQConstants</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">DELAY_EXCHANGE_NAME</span> <span class="operator">=</span> <span class="string">&quot;trade.delay.direct&quot;</span>;</span><br><span class="line">    <span class="type">String</span> <span class="variable">DELAY_ORDER_QUEUE_NAME</span> <span class="operator">=</span> <span class="string">&quot;trade.delay.order.queue&quot;</span>;</span><br><span class="line">    <span class="type">String</span> <span class="variable">DELAY_ORDER_KEY</span> <span class="operator">=</span> <span class="string">&quot;delay.order.query&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>然后编写监听消息，查询支付状态：</p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_88be4dac.png" alt="pic_88be4dac.png"></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@RequiredArgsConstructor</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderDelayMessageListener</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> IOrderService orderService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> PayClient payClient;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@RabbitListener(bindings = @QueueBinding(</span></span><br><span class="line"><span class="meta">            value = @Queue(name = MQConstants.DELAY_ORDER_QUEUE_NAME),</span></span><br><span class="line"><span class="meta">            exchange = @Exchange(name = MQConstants.DELAY_EXCHANGE_NAME, delayed = &quot;true&quot;),</span></span><br><span class="line"><span class="meta">            key = MQConstants.DELAY_ORDER_KEY</span></span><br><span class="line"><span class="meta">    ))</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">listenOrderDelayMessage</span><span class="params">(Long orderId)</span> &#123;</span><br><span class="line">        <span class="comment">// 1.查询订单</span></span><br><span class="line">        <span class="type">Order</span> <span class="variable">order</span> <span class="operator">=</span> orderService.getById(orderId);</span><br><span class="line">        <span class="comment">// 2.检测订单状态，判断是否支付</span></span><br><span class="line">        <span class="keyword">if</span>(order == <span class="literal">null</span> || order.getStatus() != <span class="number">1</span>) &#123;</span><br><span class="line">            <span class="comment">// 不存在或者是已支付</span></span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 3.未支付，需要查询支付流水状态</span></span><br><span class="line">        <span class="type">PayOrderDTO</span> <span class="variable">payOrder</span> <span class="operator">=</span> payClient.queryPayOrderByBizOrderNo(orderId);</span><br><span class="line">        <span class="comment">// 4.判断是否支付</span></span><br><span class="line">        <span class="keyword">if</span>(payOrder != <span class="literal">null</span> &amp;&amp; payOrder.getStatus() == <span class="number">3</span>)&#123;</span><br><span class="line">            <span class="comment">// 4.1.已支付，标记订单状态为已支付</span></span><br><span class="line">            orderService.markOrderPaySuccess(orderId);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 4.2.未支付，取消订单，恢复库存</span></span><br><span class="line">            orderService.cancelOrder(orderId);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>从这个类绑定的交换机不难看出它就是用来处理延时消息后的逻辑的，其中查询支付状态已经实现，我们拿到支付状态后如果已支付就可以直接对订单的状态进行更改，但是如果消息超时了就需要进行回滚操作也就是其中的 cancelOrder 方法，下面就来实现这个方法。</p>
</blockquote>
<p>cancelOrder</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">cancelOrder</span><span class="params">(Long orderId)</span> &#123;</span><br><span class="line">    <span class="comment">// 1.修改交易订单状态为已关闭</span></span><br><span class="line">    lambdaUpdate()</span><br><span class="line">            .set(Order::getStatus, <span class="number">5</span>)</span><br><span class="line">            .eq(Order::getId, orderId)</span><br><span class="line">            .update();</span><br><span class="line">    <span class="comment">// 2.修改支付状态为已取消</span></span><br><span class="line">    payClient.updatePayOrderStatusByBizOrderNo(orderId, <span class="number">2</span>);</span><br><span class="line">    <span class="comment">// 3.恢复库存</span></span><br><span class="line">    List&lt;OrderDetail&gt; list = detailService.lambdaQuery().eq(OrderDetail::getOrderId, orderId).list();</span><br><span class="line">    List&lt;OrderDetailDTO&gt; orderDetailDTOS = BeanUtils.copyToList(list, OrderDetailDTO.class);</span><br><span class="line">    itemClient.restoreStock(orderDetailDTOS);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>其中的更新支付状态以及回复库存的接口还需要我们实现</p>
</blockquote>
<p>payClient</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@FeignClient(value = &quot;pay-service&quot;, fallbackFactory = PayClientFallback.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">PayClient</span> &#123;</span><br><span class="line">    <span class="meta">@PutMapping(&quot;/pay-orders/status/&#123;id&#125;/&#123;status&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">updatePayOrderStatusByBizOrderNo</span><span class="params">(<span class="meta">@PathVariable(&quot;id&quot;)</span> Long orderId, <span class="meta">@PathVariable(&quot;status&quot;)</span> Integer status)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>payController</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@ApiOperation(&quot;修改支付订单状态&quot;)</span></span><br><span class="line"><span class="meta">@PutMapping(&quot;/status/&#123;id&#125;/&#123;status&#125;&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">updatePayOrderStatusByBizOrderNo</span><span class="params">(<span class="meta">@PathVariable(&quot;id&quot;)</span> Long orderId, <span class="meta">@PathVariable(&quot;status&quot;)</span> Integer status)</span>&#123;</span><br><span class="line">    payOrderService.updateStatusByOrderId(orderId, status);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>payServiceImpl</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">updateStatusByOrderId</span><span class="params">(Long orderId, Integer status)</span> &#123;</span><br><span class="line">    lambdaUpdate()</span><br><span class="line">            .set(PayOrder::getStatus, status)</span><br><span class="line">            .eq(PayOrder::getBizOrderNo, orderId) <span class="comment">//注意这里第一个参数是拿支付表中的订单id而不是支付id</span></span><br><span class="line">            .update();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>itemClient</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@FeignClient(&quot;item-service&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">ItemClient</span> &#123;</span><br><span class="line">    <span class="meta">@PutMapping(&quot;/items/stock/restore&quot;)</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">restoreStock</span><span class="params">(<span class="meta">@RequestBody</span> List&lt;OrderDetailDTO&gt; orderDetails)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>itemController</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@ApiOperation(&quot;恢复库存&quot;)</span></span><br><span class="line"><span class="meta">@PutMapping(&quot;/stock/restore&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">restoreStock</span><span class="params">(<span class="meta">@RequestBody</span> List&lt;OrderDetailDTO&gt; orderDetails)</span> &#123;</span><br><span class="line">    itemService.restoreStock(orderDetails);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>itemServiceImpl</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">restoreStock</span><span class="params">(List&lt;OrderDetailDTO&gt; orderDetails)</span> &#123;</span><br><span class="line">    <span class="keyword">for</span>(OrderDetailDTO orderDetail : orderDetails) &#123;</span><br><span class="line">        <span class="comment">// 根据商品id查询商品</span></span><br><span class="line">        <span class="type">Item</span> <span class="variable">item</span> <span class="operator">=</span> lambdaQuery().eq(Item::getId, orderDetail.getItemId()).one();</span><br><span class="line">        <span class="comment">// 还原库存</span></span><br><span class="line">        lambdaUpdate()</span><br><span class="line">                .set(Item :: getStock, item.getStock() + orderDetail.getNum())</span><br><span class="line">                .eq(Item::getId, orderDetail.getItemId())</span><br><span class="line">                .update();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>至此所有功能开发完毕，进行测试看商品数据是否恢复，支付状态是否更新：</p>
</blockquote>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_1f74dbeb.png" alt="pic_1f74dbeb.png"></p>
<p><img src="/img/loading.gif" data-original="https://api.smain.cn/pics/pic_8725c95f.png" alt="pic_8725c95f.png"></p>
<h2 id="5、结语"><a href="#5、结语" class="headerlink" title="5、结语"></a>5、结语</h2><blockquote>
<p>微服务架构为现代应用提供了更高的灵活性和可扩展性，同时也带来了更多的挑战。在本文中，我们通过实际案例，探讨了服务调用的全过程，分析了同步和异步调用的不同场景，并介绍了如何利用负载均衡、服务注册与发现等关键技术来实现微服务间的高效通信。</p>
<p>尽管微服务架构能极大提升开发和部署的效率，但在实际应用中，我们仍需面对分布式事务、服务治理等问题。未来的学习中，深入理解这些问题并探索解决方案，将帮助我们构建更可靠、更高效的分布式系统。</p>
<p>希望这篇文章能为你在微服务的学习和实践上提供一些思路，也期待我们在这个领域不断积累经验，迎接更多的挑战。</p>
</blockquote>
</article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta"><i class="fas fa-circle-user fa-fw"></i>文章作者: </span><span class="post-copyright-info"><a href="https://ywj-ch.github.io">爱吃薯片的熊猫</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta"><i class="fas fa-square-arrow-up-right fa-fw"></i>文章链接: </span><span class="post-copyright-info"><a href="https://ywj-ch.github.io/2025/01/25/%E5%BE%AE%E6%9C%8D%E5%8A%A1%E5%85%A5%E9%97%A8%EF%BC%9A%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8%E6%B5%81%E7%A8%8B%E8%A7%A3%E6%9E%90/">https://ywj-ch.github.io/2025/01/25/%E5%BE%AE%E6%9C%8D%E5%8A%A1%E5%85%A5%E9%97%A8%EF%BC%9A%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8%E6%B5%81%E7%A8%8B%E8%A7%A3%E6%9E%90/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta"><i class="fas fa-circle-exclamation fa-fw"></i>版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来源 <a href="https://ywj-ch.github.io" target="_blank">爱吃薯片的熊猫の技术小站</a>！</span></div></div><div class="tag_share"><div class="post-meta__tag-list"><a class="post-meta__tags" href="/tags/springcloud/">springcloud</a><a class="post-meta__tags" href="/tags/%E5%BE%AE%E6%9C%8D%E5%8A%A1/">微服务</a></div><div class="post-share"><div class="social-share" data-image="/img/SpringCloud%20%E5%BE%AE%E6%9C%8D%E5%8A%A1%E5%85%A5%E9%97%A8%EF%BC%9A%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8%E6%B5%81%E7%A8%8B%E8%A7%A3%E6%9E%90.png" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://lib.baomitu.com/social-share.js/1.0.16/css/share.min.css" media="print" onload="this.media='all'"><script src="https://lib.baomitu.com/social-share.js/1.0.16/js/social-share.min.js" defer></script></div></div><nav class="pagination-post" id="pagination"><a class="pagination-related" href="/2025/01/12/Linux%EF%BC%88Centos7%EF%BC%89%E5%AE%89%E8%A3%85docker%E3%80%81mysql%E8%B8%A9%E5%9D%91%E6%80%BB%E7%BB%93/" title="Linux（Centos7）安装docker、mysql踩坑总结"><img class="cover" src="/img/loading.gif" data-original="/img/Linux%EF%BC%88Centos7%EF%BC%89%E5%AE%89%E8%A3%85docker%E3%80%81mysql%E8%B8%A9%E5%9D%91%E6%80%BB%E7%BB%93.png" onerror="onerror=null;src='/img/404.jpg'" alt="cover of previous post"><div class="info"><div class="info-1"><div class="info-item-1">上一篇</div><div class="info-item-2">Linux（Centos7）安装docker、mysql踩坑总结</div></div><div class="info-2"><div class="info-item-1"> 本文主要是记录了在 CentOS 7 上安装 Docker 和 MySQL 时遇到的一些问题，主要是由于镜像源未配置正确，导致无法顺利下载所需的依赖包。下面将介绍在安装过程中遇到的困难，并分享如何通过配置合适的镜像源来解决这些问题，从而顺利完成 Docker 和 MySQL 的安装，希望能够帮到有需要的人。  一、安装准备 系统版本：CentOS 7 先安装 yum：  yum install -y yum-utils device-mapper-persistent-data lvm2   执行之前先配置一下镜像源，输入以下命令进入配置文件：  vim /etc/yum.repos.d/CentOS-Base.repo   再将 mirrorlist 注释掉然后将 baseurl 改为阿里云镜像，然后保存退出    一定要将 mirrorlist 注释掉！不然还是会直接访问官方源导致下载失败！   输入下面的命令检验是否安装成功：    当然不排除网络问题，可以先用 ping 命令测试一下网络是否连通：    只要网络连通，并且配置文件修改无误就肯定能安装成功。  二、安装...</div></div></div></a><a class="pagination-related" href="/2025/02/11/Spring%20Boot%20+%20Vue%20%E5%89%8D%E5%90%8E%E7%AB%AF%E5%88%86%E7%A6%BB%E9%A1%B9%E7%9B%AE%E4%B8%8A%E7%BA%BF%E5%AE%9E%E8%AE%B0/" title="Spring Boot + Vue 前后端分离项目上线实记"><img class="cover" src="/img/loading.gif" data-original="/img/%E9%A1%B9%E7%9B%AE%E4%B8%8A%E7%BA%BF.png" onerror="onerror=null;src='/img/404.jpg'" alt="cover of next post"><div class="info text-right"><div class="info-1"><div class="info-item-1">下一篇</div><div class="info-item-2">Spring Boot + Vue 前后端分离项目上线实记</div></div><div class="info-2"><div class="info-item-1">一、前言 本文记录了一个前后端分离项目的完整部署流程。许多同学在本地开发完项目后，缺乏将其部署到远程服务器的实战经验。因此，我将以一个实际项目为例，详细展示从环境准备到最终上线的全过程，希望对你有所帮助！  二、服务器环境准备2.1、配置服务器 首先准备一个服务器，我这里用的是阿里云 ：     第一次购买一般都会有优惠，而且对于一般的单体项目而言 2 核 2G 的配置也够用了，当然也有 3 个月的试用版本。   完成之后进入控制台第一件事就是去你左侧边栏的安全组里面开放端口，这样才能从远程访问。     端口范围根据需求开放，一般是 3306 mysql、80 nginx 代理、13103 这里是宝塔面板的端口，什么是宝塔面板后面会讲到，下面 3 个端口是默认初始化好的。   然后进入实例面板重新设置一下你的服务器密码：    修改完成之后就可以试着连接你的服务器了，我这里使用的是...</div></div></div></a></nav></div><div class="aside-content" id="aside-content"><div class="card-widget card-info text-center"><div class="avatar-img"><img src="/img/loading.gif" data-original="/img/butterfly-icon.png" onerror="this.onerror=null;this.src='/img/friend_404.gif'" alt="avatar"/></div><div class="author-info-name">爱吃薯片的熊猫</div><div class="author-info-description">Dream big Work hard Stay focused</div><div class="site-data"><a href="/archives/"><div class="headline">文章</div><div class="length-num">6</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">9</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">3</div></a></div><a id="card-info-btn" target="_blank" rel="noopener" href="https://github.com/Ywj-ch"><i class="fab fa-github"></i><span>Follow Me</span></a><div class="card-info-social-icons"><a class="social-icon" href="https://blog.csdn.net/m0_74123949" target="_blank" title="CSDN"><i class="fas fa-book" style="color: #d81e06;"></i></a><a class="social-icon" href="https://gitee.com/Ywj-ee" target="_blank" title="Gitee"><i class="fas fa-code-branch" style="color: #C71D23;"></i></a></div></div><div class="card-widget card-announcement"><div class="item-headline"><i class="fas fa-bullhorn fa-shake"></i><span>公告</span></div><div class="announcement_content">欢迎来到我的博客！</div></div><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span><span class="toc-percentage"></span></div><div class="toc-content"><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#1%E3%80%81%E5%BC%95%E8%A8%80"><span class="toc-number">1.</span> <span class="toc-text">1、引言</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1%E5%9F%BA%E7%A1%80%E6%A6%82%E5%BF%B5"><span class="toc-number">2.</span> <span class="toc-text">2、微服务基础概念</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#2-1%E3%80%81%E6%A1%86%E6%9E%B6%E6%80%BB%E8%A7%88"><span class="toc-number">2.1.</span> <span class="toc-text">2.1、框架总览</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-2%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1%E4%B8%8E%E5%8D%95%E4%BD%93%E6%9E%B6%E6%9E%84%E7%9A%84%E5%AF%B9%E6%AF%94"><span class="toc-number">2.2.</span> <span class="toc-text">2.2、微服务与单体架构的对比</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3%E3%80%81%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%9A%84%E6%9C%80%E5%A4%A7%E7%89%B9%E7%82%B9%EF%BC%9A%E6%9C%8D%E5%8A%A1%E4%B9%8B%E9%97%B4%E7%9A%84%E8%B0%83%E7%94%A8"><span class="toc-number">3.</span> <span class="toc-text">3、微服务的最大特点：服务之间的调用</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#3-1%E3%80%81%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C"><span class="toc-number">3.1.</span> <span class="toc-text">3.1、服务注册</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#3-1-1%E3%80%81%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83%E5%8E%9F%E7%90%86"><span class="toc-number">3.1.1.</span> <span class="toc-text">3.1.1、注册中心原理</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3-1-2%E3%80%81Nacos-%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83"><span class="toc-number">3.1.2.</span> <span class="toc-text">3.1.2、Nacos 注册中心</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3-1-3%E3%80%81%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C"><span class="toc-number">3.1.3.</span> <span class="toc-text">3.1.3、服务注册</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-2%E3%80%81%E6%9C%8D%E5%8A%A1%E9%97%B4%E9%80%9A%E4%BF%A1%E6%96%B9%E5%BC%8F"><span class="toc-number">3.2.</span> <span class="toc-text">3.2、服务间通信方式</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#3-2-1%E3%80%81OpenFeign"><span class="toc-number">3.2.1.</span> <span class="toc-text">3.2.1、OpenFeign</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3-2-2%E3%80%81RabbitMQ"><span class="toc-number">3.2.2.</span> <span class="toc-text">3.2.2、RabbitMQ</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3-2-3%E3%80%81SpringAMQP"><span class="toc-number">3.2.3.</span> <span class="toc-text">3.2.3、SpringAMQP</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-3%E3%80%81%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1"><span class="toc-number">3.3.</span> <span class="toc-text">3.3、负载均衡</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#3-3-1%E3%80%81%E6%BA%90%E7%A0%81%E8%B7%9F%E8%B8%AA"><span class="toc-number">3.3.1.</span> <span class="toc-text">3.3.1、源码跟踪</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3-3-2%E3%80%81NacosRule"><span class="toc-number">3.3.2.</span> <span class="toc-text">3.3.2、NacosRule</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-4%E3%80%81-%E6%9C%8D%E5%8A%A1%E5%AE%B9%E9%94%99%E6%9C%BA%E5%88%B6"><span class="toc-number">3.4.</span> <span class="toc-text">3.4、 服务容错机制</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#3-4-1%E3%80%81%E6%9C%8D%E5%8A%A1%E4%BF%9D%E6%8A%A4%E6%96%B9%E6%A1%88"><span class="toc-number">3.4.1.</span> <span class="toc-text">3.4.1、服务保护方案</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3-4-2%E3%80%81Sentinel"><span class="toc-number">3.4.2.</span> <span class="toc-text">3.4.2、Sentinel</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3-4-3%E3%80%81%E8%AF%B7%E6%B1%82%E9%99%90%E6%B5%81"><span class="toc-number">3.4.3.</span> <span class="toc-text">3.4.3、请求限流</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3-4-4%E3%80%81%E7%BA%BF%E7%A8%8B%E9%9A%94%E7%A6%BB"><span class="toc-number">3.4.4.</span> <span class="toc-text">3.4.4、线程隔离</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3-4-5%E3%80%81%E6%9C%8D%E5%8A%A1%E7%86%94%E6%96%AD"><span class="toc-number">3.4.5.</span> <span class="toc-text">3.4.5、服务熔断</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-5%E3%80%81%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1"><span class="toc-number">3.5.</span> <span class="toc-text">3.5、分布式事务</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#3-5-1%E3%80%81Seata"><span class="toc-number">3.5.1.</span> <span class="toc-text">3.5.1、Seata</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3-5-2%E3%80%81XA-%E6%A8%A1%E5%BC%8F"><span class="toc-number">3.5.2.</span> <span class="toc-text">3.5.2、XA 模式</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3-5-3%E3%80%81AT-%E6%A8%A1%E5%BC%8F"><span class="toc-number">3.5.3.</span> <span class="toc-text">3.5.3、AT 模式</span></a></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4%E3%80%81%E6%A1%88%E4%BE%8B%E6%BC%94%E7%A4%BA%EF%BC%9A%E5%BE%AE%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8%E7%9A%84%E5%AE%8C%E6%95%B4%E6%B5%81%E7%A8%8B"><span class="toc-number">4.</span> <span class="toc-text">4、案例演示：微服务调用的完整流程</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5%E3%80%81%E7%BB%93%E8%AF%AD"><span class="toc-number">5.</span> <span class="toc-text">5、结语</span></a></li></ol></div></div><div class="card-widget card-recent-post"><div class="item-headline"><i class="fas fa-history"></i><span>最新文章</span></div><div class="aside-list"><div class="aside-list-item"><a class="thumbnail" href="/2025/02/11/%E5%9F%BA%E4%BA%8EHexo%E6%A1%86%E6%9E%B6%E5%92%8CButterfly%E4%B8%BB%E9%A2%98%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2%E7%BD%91%E7%AB%99/" title="基于Hexo框架和Butterfly主题搭建个人博客网站"><img src="/img/loading.gif" data-original="/img/%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA%E6%95%99%E7%A8%8B.png" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="基于Hexo框架和Butterfly主题搭建个人博客网站"/></a><div class="content"><a class="title" href="/2025/02/11/%E5%9F%BA%E4%BA%8EHexo%E6%A1%86%E6%9E%B6%E5%92%8CButterfly%E4%B8%BB%E9%A2%98%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2%E7%BD%91%E7%AB%99/" title="基于Hexo框架和Butterfly主题搭建个人博客网站">基于Hexo框架和Butterfly主题搭建个人博客网站</a><time datetime="2025-02-11T13:37:26.000Z" title="发表于 2025-02-11 21:37:26">2025-02-11</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/2025/02/11/Spring%20Boot%20+%20Vue%20%E5%89%8D%E5%90%8E%E7%AB%AF%E5%88%86%E7%A6%BB%E9%A1%B9%E7%9B%AE%E4%B8%8A%E7%BA%BF%E5%AE%9E%E8%AE%B0/" title="Spring Boot + Vue 前后端分离项目上线实记"><img src="/img/loading.gif" data-original="/img/%E9%A1%B9%E7%9B%AE%E4%B8%8A%E7%BA%BF.png" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="Spring Boot + Vue 前后端分离项目上线实记"/></a><div class="content"><a class="title" href="/2025/02/11/Spring%20Boot%20+%20Vue%20%E5%89%8D%E5%90%8E%E7%AB%AF%E5%88%86%E7%A6%BB%E9%A1%B9%E7%9B%AE%E4%B8%8A%E7%BA%BF%E5%AE%9E%E8%AE%B0/" title="Spring Boot + Vue 前后端分离项目上线实记">Spring Boot + Vue 前后端分离项目上线实记</a><time datetime="2025-02-11T13:29:31.000Z" title="发表于 2025-02-11 21:29:31">2025-02-11</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/2025/01/25/%E5%BE%AE%E6%9C%8D%E5%8A%A1%E5%85%A5%E9%97%A8%EF%BC%9A%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8%E6%B5%81%E7%A8%8B%E8%A7%A3%E6%9E%90/" title="SpringCloud 微服务入门：服务调用流程解析"><img src="/img/loading.gif" data-original="/img/SpringCloud%20%E5%BE%AE%E6%9C%8D%E5%8A%A1%E5%85%A5%E9%97%A8%EF%BC%9A%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8%E6%B5%81%E7%A8%8B%E8%A7%A3%E6%9E%90.png" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="SpringCloud 微服务入门：服务调用流程解析"/></a><div class="content"><a class="title" href="/2025/01/25/%E5%BE%AE%E6%9C%8D%E5%8A%A1%E5%85%A5%E9%97%A8%EF%BC%9A%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8%E6%B5%81%E7%A8%8B%E8%A7%A3%E6%9E%90/" title="SpringCloud 微服务入门：服务调用流程解析">SpringCloud 微服务入门：服务调用流程解析</a><time datetime="2025-01-25T12:08:31.000Z" title="发表于 2025-01-25 20:08:31">2025-01-25</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/2025/01/12/Linux%EF%BC%88Centos7%EF%BC%89%E5%AE%89%E8%A3%85docker%E3%80%81mysql%E8%B8%A9%E5%9D%91%E6%80%BB%E7%BB%93/" title="Linux（Centos7）安装docker、mysql踩坑总结"><img src="/img/loading.gif" data-original="/img/Linux%EF%BC%88Centos7%EF%BC%89%E5%AE%89%E8%A3%85docker%E3%80%81mysql%E8%B8%A9%E5%9D%91%E6%80%BB%E7%BB%93.png" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="Linux（Centos7）安装docker、mysql踩坑总结"/></a><div class="content"><a class="title" href="/2025/01/12/Linux%EF%BC%88Centos7%EF%BC%89%E5%AE%89%E8%A3%85docker%E3%80%81mysql%E8%B8%A9%E5%9D%91%E6%80%BB%E7%BB%93/" title="Linux（Centos7）安装docker、mysql踩坑总结">Linux（Centos7）安装docker、mysql踩坑总结</a><time datetime="2025-01-12T13:05:12.000Z" title="发表于 2025-01-12 21:05:12">2025-01-12</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/2025/01/04/%E7%94%A8Python%E4%B8%8EFiddler%E5%AE%9E%E7%8E%B0%E5%9B%BE%E4%B9%A6%E9%A6%86%E5%BA%A7%E4%BD%8D%E8%87%AA%E5%8A%A8%E9%A2%84%E7%BA%A6%EF%BC%9A%E5%AE%9E%E6%88%98%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/" title="用Python与Fiddler实现图书馆座位自动预约：实战经验分享"><img src="/img/loading.gif" data-original="/img/%E7%94%A8Python%E4%B8%8EFiddler%E5%AE%9E%E7%8E%B0%E5%9B%BE%E4%B9%A6%E9%A6%86%E5%BA%A7%E4%BD%8D%E8%87%AA%E5%8A%A8%E9%A2%84%E7%BA%A6%EF%BC%9A%E5%AE%9E%E6%88%98%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB.png" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="用Python与Fiddler实现图书馆座位自动预约：实战经验分享"/></a><div class="content"><a class="title" href="/2025/01/04/%E7%94%A8Python%E4%B8%8EFiddler%E5%AE%9E%E7%8E%B0%E5%9B%BE%E4%B9%A6%E9%A6%86%E5%BA%A7%E4%BD%8D%E8%87%AA%E5%8A%A8%E9%A2%84%E7%BA%A6%EF%BC%9A%E5%AE%9E%E6%88%98%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/" title="用Python与Fiddler实现图书馆座位自动预约：实战经验分享">用Python与Fiddler实现图书馆座位自动预约：实战经验分享</a><time datetime="2025-01-04T05:21:05.000Z" title="发表于 2025-01-04 13:21:05">2025-01-04</time></div></div></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">&copy;2024 - 2025 By 爱吃薯片的熊猫</div><div class="footer_custom_text"><span style='color:#FFD700; font-weight:bold;'>竹子用了4年的时间， 仅仅长了3cm， 从第五年开始， 以每天30cm的速度疯狂地生长， 仅仅用了六周的时间就长到了15米。</span><br>
<span style='color:#87CEEB; font-weight:bold;'>其实，在前面的四年， 竹子将根在土壤里延伸了数百平米。 做人做事亦是如此， 不要担心你此时此刻的付出得不到回报， 因为这些付出都是为了扎根。</span>
</div></div><!-- 添加网站运行时间显示的容器--><div id="runtimeshow" data-publishDate="2025-02-01"><span>小站已运行了：</span><span id="runtime-counter"></span><span class="sand-clock">⏳</span></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="darkmode" type="button" title="日间和夜间模式切换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside-config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><button id="go-up" type="button" title="回到顶部"><span class="scroll-percent"></span><i class="fas fa-arrow-up"></i></button></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><div class="js-pjax"></div><canvas id="universe"></canvas><script defer src="/js/universe.js"></script><script defer src="/js/cursor.js"></script><script src="/js/sun_moon.js" async></script><script async src="/js/fps.js"></script><script async src="/js/title.js"></script><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><div id="local-search"><div class="search-dialog"><nav class="search-nav"><span class="search-dialog-title">搜索</span><span id="loading-status"></span><button class="search-close-button"><i class="fas fa-times"></i></button></nav><div class="text-center" id="loading-database"><i class="fas fa-spinner fa-pulse"></i><span>  数据加载中</span></div><div class="search-wrap"><div id="local-search-input"><div class="local-search-box"><input class="local-search-box--input" placeholder="Search for Posts" type="text"/></div></div><hr/><div id="local-search-results"></div><div id="local-search-stats-wrap"></div></div></div><div id="search-mask"></div><script src="/js/search/local-search.js"></script></div></div><!-- hexo injector body_end start --> <script data-pjax>if(document.getElementById('recent-posts') && (location.pathname ==='/'|| '/' ==='all')){
    var parent = document.getElementById('recent-posts');
    var child = '<div class="recent-post-item" style="width:100%;height: auto"><div id="catalog_magnet"><div class="magnet_item"><a class="magnet_link" href="https://ywj-ch.github.io/categories/学习笔记/"><div class="magnet_link_context" style=""><span style="font-weight:500;flex:1">📚 爱吃薯片的熊猫の学习笔记 (4)</span><span style="padding:0px 4px;border-radius: 8px;"><i class="fas fa-arrow-circle-right"></i></span></div></a></div><div class="magnet_item"><a class="magnet_link" href="https://ywj-ch.github.io/categories/个人项目/"><div class="magnet_link_context" style=""><span style="font-weight:500;flex:1">👩‍💻 爱吃薯片的熊猫の个人项目 (1)</span><span style="padding:0px 4px;border-radius: 8px;"><i class="fas fa-arrow-circle-right"></i></span></div></a></div><a class="magnet_link_more"  href="https://ywj-ch.github.io/categories" style="flex:1;text-align: center;margin-bottom: 10px;">查看更多...</a></div></div>';
    console.log('已挂载magnet')
    parent.insertAdjacentHTML("afterbegin",child)}
     </script><style>#catalog_magnet{flex-wrap: wrap;display: flex;width:100%;justify-content:space-between;padding: 10px 10px 0 10px;align-content: flex-start;}.magnet_item{flex-basis: calc(50% - 5px);background: #f2f2f2;margin-bottom: 10px;border-radius: 8px;transition: all 0.2s ease-in-out;}.magnet_item:hover{background: #69e8f2}.magnet_link_more{color:#555}.magnet_link{color:black}.magnet_link:hover{color:white}@media screen and (max-width: 600px) {.magnet_item {flex-basis: 100%;}}.magnet_link_context{display:flex;padding: 10px;font-size:16px;transition: all 0.2s ease-in-out;}.magnet_link_context:hover{padding: 10px 20px;}</style>
    <style></style><script data-pjax>
  function butterfly_swiper_injector_config(){
    var parent_div_git = document.getElementById('recent-posts');
    var item_html = '<div class="recent-post-item" style="height: auto;width: 100%"><div class="blog-slider swiper-container-fade swiper-container-horizontal" id="swiper_container"><div class="blog-slider__wrp swiper-wrapper" style="transition-duration: 0ms;"><div class="blog-slider__item swiper-slide" style="width: 750px; opacity: 1; transform: translate3d(0px, 0px, 0px); transition-duration: 0ms;"><a class="blog-slider__img" href="2025/01/25/微服务入门：服务调用流程解析/" alt=""><img width="48" height="48" src="/img/loading.gif" data-original="/img/SpringCloud 微服务入门：服务调用流程解析.png" alt="" onerror="this.src=https://unpkg.zhimg.com/akilar-candyassets/image/loading.gif; this.onerror = null;"/></a><div class="blog-slider__content"><span class="blog-slider__code">2025-01-25</span><a class="blog-slider__title" href="2025/01/25/微服务入门：服务调用流程解析/" alt="">SpringCloud 微服务入门：服务调用流程解析</a><div class="blog-slider__text">记录了在 CentOS 7 上安装 Docker 和 MySQL 时遇到的问题及解决方案，帮助你更顺利地搭建环境，避免常见的坑。</div><a class="blog-slider__button" href="2025/01/25/微服务入门：服务调用流程解析/" alt="">详情   </a></div></div><div class="blog-slider__item swiper-slide" style="width: 750px; opacity: 1; transform: translate3d(0px, 0px, 0px); transition-duration: 0ms;"><a class="blog-slider__img" href="2025/02/11/基于Hexo框架和Butterfly主题搭建个人博客网站/" alt=""><img width="48" height="48" src="/img/loading.gif" data-original="/img/个人博客搭建教程.png" alt="" onerror="this.src=https://unpkg.zhimg.com/akilar-candyassets/image/loading.gif; this.onerror = null;"/></a><div class="blog-slider__content"><span class="blog-slider__code">2025-02-11</span><a class="blog-slider__title" href="2025/02/11/基于Hexo框架和Butterfly主题搭建个人博客网站/" alt="">基于Hexo框架和Butterfly主题搭建个人博客网站</a><div class="blog-slider__text">在这篇博客中，我将带你一起走过基于 Hexo 框架搭建个人博客的全过程，同时介绍如何使用 Butterfly 主题来美化博客，使其更加符合个人风格。</div><a class="blog-slider__button" href="2025/02/11/基于Hexo框架和Butterfly主题搭建个人博客网站/" alt="">详情   </a></div></div><div class="blog-slider__item swiper-slide" style="width: 750px; opacity: 1; transform: translate3d(0px, 0px, 0px); transition-duration: 0ms;"><a class="blog-slider__img" href="2025/02/11/Spring Boot + Vue 前后端分离项目上线实记/" alt=""><img width="48" height="48" src="/img/loading.gif" data-original="/img/项目上线.png" alt="" onerror="this.src=https://unpkg.zhimg.com/akilar-candyassets/image/loading.gif; this.onerror = null;"/></a><div class="blog-slider__content"><span class="blog-slider__code">2025-02-11</span><a class="blog-slider__title" href="2025/02/11/Spring Boot + Vue 前后端分离项目上线实记/" alt="">Spring Boot + Vue 前后端分离项目上线实记</a><div class="blog-slider__text">记录了一个前后端分离项目的完整部署流程。</div><a class="blog-slider__button" href="2025/02/11/Spring Boot + Vue 前后端分离项目上线实记/" alt="">详情   </a></div></div><div class="blog-slider__item swiper-slide" style="width: 750px; opacity: 1; transform: translate3d(0px, 0px, 0px); transition-duration: 0ms;"><a class="blog-slider__img" href="2025/01/12/Linux（Centos7）安装docker、mysql踩坑总结/" alt=""><img width="48" height="48" src="/img/loading.gif" data-original="/img/Linux（Centos7）安装docker、mysql踩坑总结.png" alt="" onerror="this.src=https://unpkg.zhimg.com/akilar-candyassets/image/loading.gif; this.onerror = null;"/></a><div class="blog-slider__content"><span class="blog-slider__code">2025-01-12</span><a class="blog-slider__title" href="2025/01/12/Linux（Centos7）安装docker、mysql踩坑总结/" alt="">Linux（Centos7）安装docker、mysql踩坑总结</a><div class="blog-slider__text">记录了在 CentOS 7 上安装 Docker 和 MySQL 时遇到的问题及解决方案，帮助你更顺利地搭建环境，避免常见的坑。</div><a class="blog-slider__button" href="2025/01/12/Linux（Centos7）安装docker、mysql踩坑总结/" alt="">详情   </a></div></div><div class="blog-slider__item swiper-slide" style="width: 750px; opacity: 1; transform: translate3d(0px, 0px, 0px); transition-duration: 0ms;"><a class="blog-slider__img" href="2025/01/04/用Python与Fiddler实现图书馆座位自动预约：实战经验分享/" alt=""><img width="48" height="48" src="/img/loading.gif" data-original="/img/用Python与Fiddler实现图书馆座位自动预约：实战经验分享.png" alt="" onerror="this.src=https://unpkg.zhimg.com/akilar-candyassets/image/loading.gif; this.onerror = null;"/></a><div class="blog-slider__content"><span class="blog-slider__code">2025-01-04</span><a class="blog-slider__title" href="2025/01/04/用Python与Fiddler实现图书馆座位自动预约：实战经验分享/" alt="">用Python与Fiddler实现图书馆座位自动预约：实战经验分享</a><div class="blog-slider__text">本文详细介绍了基于若依框架构建 SpringBoot 管理系统的学习过程，涵盖了框架的基本配置、功能实现及常见问题解决，帮助开发者快速上手和掌握若依框架。</div><a class="blog-slider__button" href="2025/01/04/用Python与Fiddler实现图书馆座位自动预约：实战经验分享/" alt="">详情   </a></div></div><div class="blog-slider__item swiper-slide" style="width: 750px; opacity: 1; transform: translate3d(0px, 0px, 0px); transition-duration: 0ms;"><a class="blog-slider__img" href="2024/10/24/基于若依框架的SpringBoot管理系统学习/" alt=""><img width="48" height="48" src="/img/loading.gif" data-original="/img/基于若依框架的SpringBoot管理系统学习.png" alt="" onerror="this.src=https://unpkg.zhimg.com/akilar-candyassets/image/loading.gif; this.onerror = null;"/></a><div class="blog-slider__content"><span class="blog-slider__code">2024-10-24</span><a class="blog-slider__title" href="2024/10/24/基于若依框架的SpringBoot管理系统学习/" alt="">基于若依框架的SpringBoot管理系统学习</a><div class="blog-slider__text">本文详细介绍了基于若依框架构建 SpringBoot 管理系统的学习过程，涵盖了框架的基本配置、功能实现及常见问题解决，帮助开发者快速上手和掌握若依框架。</div><a class="blog-slider__button" href="2024/10/24/基于若依框架的SpringBoot管理系统学习/" alt="">详情   </a></div></div></div><div class="blog-slider__pagination swiper-pagination-clickable swiper-pagination-bullets"></div></div></div>';
    console.log('已挂载butterfly_swiper')
    parent_div_git.insertAdjacentHTML("afterbegin",item_html)
    }
  var elist = 'undefined'.split(',');
  var cpage = location.pathname;
  var epage = '/';
  var flag = 0;

  for (var i=0;i<elist.length;i++){
    if (cpage.includes(elist[i])){
      flag++;
    }
  }

  if ((epage ==='all')&&(flag == 0)){
    butterfly_swiper_injector_config();
  }
  else if (epage === cpage){
    butterfly_swiper_injector_config();
  }
  </script><script defer src="https://npm.elemecdn.com/hexo-butterfly-swiper/lib/swiper.min.js"></script><script defer data-pjax src="https://npm.elemecdn.com/hexo-butterfly-swiper/lib/swiper_init.js"></script><div class="js-pjax"><script async="async">var arr = document.getElementsByClassName('recent-post-item');
for(var i = 0;i<arr.length;i++){
    arr[i].classList.add('wow');
    arr[i].classList.add('animate__fadeInDown');
    arr[i].setAttribute('data-wow-duration', '2s');
    arr[i].setAttribute('data-wow-delay', '1s');
    arr[i].setAttribute('data-wow-offset', '100');
    arr[i].setAttribute('data-wow-iteration', '2');
  }</script><script async="async">var arr = document.getElementsByClassName('card-widget');
for(var i = 0;i<arr.length;i++){
    arr[i].classList.add('wow');
    arr[i].classList.add('animate__zoomIn');
    arr[i].setAttribute('data-wow-duration', '');
    arr[i].setAttribute('data-wow-delay', '');
    arr[i].setAttribute('data-wow-offset', '');
    arr[i].setAttribute('data-wow-iteration', '');
  }</script></div><script defer src="https://npm.elemecdn.com/hexo-butterfly-wowjs/lib/wow.min.js"></script><script defer src="https://npm.elemecdn.com/hexo-butterfly-wowjs/lib/wow_init.js"></script><!-- hexo injector body_end end -->
        <style>
            [bg-lazy] {
                background-image: none !important;
                background-color: #eee !important;
            }
        </style>
        <script>
            window.imageLazyLoadSetting = {
                isSPA: false,
                preloadRatio: 1,
                processImages: null,
            };
        </script><script>window.addEventListener("load",function(){var t=/\.(gif|jpg|jpeg|tiff|png)$/i,r=/^data:image\/[a-z\d\-\.\+]+;base64,/;Array.prototype.slice.call(document.querySelectorAll("img[data-original]")).forEach(function(a){var e=a.parentNode;"A"===e.tagName&&(t.test(e.href)||r.test(e.href))&&(e.href=a.dataset.original)})});</script><script>(r=>{r.imageLazyLoadSetting.processImages=t;var a=r.imageLazyLoadSetting.isSPA,o=r.imageLazyLoadSetting.preloadRatio||1,d=i();function i(){var t=Array.prototype.slice.call(document.querySelectorAll("img[data-original]")),e=Array.prototype.slice.call(document.querySelectorAll("[bg-lazy]"));return t.concat(e)}function t(t){(a||t)&&(d=i());for(var e,n=0;n<d.length;n++)0<=(e=(e=d[n]).getBoundingClientRect()).bottom&&0<=e.left&&e.top<=(r.innerHeight*o||document.documentElement.clientHeight*o)&&(()=>{var t,e,a,o,i=d[n];e=function(){d=d.filter(function(t){return i!==t}),r.imageLazyLoadSetting.onImageLoaded&&r.imageLazyLoadSetting.onImageLoaded(i)},(t=i).dataset.loaded||(t.hasAttribute("bg-lazy")?(t.removeAttribute("bg-lazy"),e&&e()):(a=new Image,o=t.getAttribute("data-original"),a.onload=function(){t.src=o,t.removeAttribute("data-original"),t.setAttribute("data-loaded",!0),e&&e()},a.onerror=function(){t.removeAttribute("data-original"),t.setAttribute("data-loaded",!1),t.src=o},t.src!==o&&(a.src=o)))})()}function e(){clearTimeout(t.tId),t.tId=setTimeout(t,500)}t(),document.addEventListener("scroll",e),r.addEventListener("resize",e),r.addEventListener("orientationchange",e)})(this);</script><script src="/live2dw/lib/L2Dwidget.min.js?094cbace49a39548bed64abff5988b05"></script><script>L2Dwidget.init({"pluginRootPath":"live2dw/","pluginJsPath":"lib/","pluginModelPath":"assets/","tagMode":false,"debug":false,"model":{"jsonPath":"/live2dw/assets/koharu.model.json"},"display":{"position":"left","width":150,"height":300},"mobile":{"show":false},"log":false});</script></body></html>